Did SwiftUI Just Kill Protocol-Oriented Programming?

SwiftUI’s state management is amazing… but it comes with a steep price.

Michael Long
Jan 21 · 6 min read
Image for post
Image for post
Balloon Pop by KellyJane on DeviantArt

During WWDC 2015 Apple dropped a small bombshell on the Swift development community during a session titled “Protocol-Oriented Programming in Swift”. In it Apple demonstrated just how Protocol-Oriented Programming (POP) could free us from the constraints of traditional class-based object hierarchies.

Swift hasn’t been the same since.

Then, just over a year ago during WWDC 2019, Apple dropped another nuke and introduced SwiftUI — its proposed user-interface replacement for the ever venerable UIKit and AppKit.

There’s just one problem.

SwiftUI and Protocol-Oriented Programming don’t get along very well.

To see why, let’s start with an example.

Say that we’ve begun a new SwiftUI project and that we’re writing YAARR: Yet-Another-Another-RSS-Reader.

At its most basic level YAARR will need an observable object that provides a list of articles. And let’s say that object currently looks like the following:

Standard, everyday, run-of-the-mill SwiftUI. Call load() and eventually the two published values will be set and our app will see the changes and display our list of RSS articles. Wonderful.

But this was an article about Protocol-Oriented Programming. Well, one of the most common uses for POP lies in creating protocols that let us mock data. Let’s turn FeedProvider into a protocol and make our object conform to it.

Straightforward enough. But the second we try to use it our troubles begin.

Enter the above code and Xcode will immediately throw two errors at you on the EnvironmentObject line.

The second of the two errors is the one that tends to send chills down that back of any seasoned iOS Swift developer: the dreaded self or associated type requirements problem. But our object code looks fine. We don’t do any of that nasty associated-type nonsense. Or do we?

Unfortunately, we do, because in order to work correctly with SwiftUI our protocol needs to be an ObservableObject, and that contains an associated type.

If our type isn’t an ObservableObject, then it can’t be used as a StateObject or passed and used in environment objects.

What to do?

Let’s back up and take another look at the problem. We fail because we lack an associated type… but what happens when we give it one?

A bit ugly… but it works and our errors go away! Here. Unfortunately, we’ve just kicked the can further down the road because Swift is now complaining about our container.

The next error is now on theFeedView() line, where it tells us that our newly minted generic parameter 'F' could not be inferred. Fine. We can fix that by explicitly telling it the type…

But… that means we’ve now encoded the type into our code and it can’t be changed.

We can’t, for example, make a factory method that returns FeedProviding because FeedProviding has associated type requirements.

We can’t use some FeedProviding as a function result because the function will still only be able to return a single type.

We can’t use dependency injection system like Resolver because we will once more run aground on the shoals of the self or associated type requirements issue when Resolver attempts to infer the required type.

In short, ObservableObjects and protocols simply don’t mix.

And that pretty much kills using protocol-based objects in SwiftUI.

Or does it?

Let’s take yet another step back and examine things from another angle. Our core issue stems from the fact that we’re trying to join our observable objects and our protocols in some sort of unholy object-oriented matrimony.

But what if we add a level of indirection? What if our observable objects contained our protocols? Consider the following protocol…

And then the observable object…

Here I’ve used a simple callback closure instead of Combine or some other more advanced reactive technique, but the principle is the same.

Our ObservableObject is once more a simple observable object. But it just so happens that our FeedProvider delegates most of its work to the FeedLoader service when our observable object’s load function is called.

Our FeedLoader is an instance of our FeedLoading protocol. Here I’ve made it a simple lazy variable on FeedProvider so it can be replaced as needed, but in production code I’d probably inject the loader using Resolver.

With the above in place our view is back to being straight SwiftUI as well.

Mocking is straightforward: just give our observable object a different loader. In fact, to make things easier for my various previews I’d probably define a standard mock version of my provider.

To be consumed as needed.

To be completely fair, you could set the published variables directly, but swapping out the loader also lets you test more of the paths through your code, which in turn allows you to see your problems sooner.

You can also create multiple mock loaders and versions to test various scenarios (no articles, one article, multiple articles, errors, etc.). This can come in very in handy when using multiple previews and in your unit tests. (You are doing unit tests, aren’t you?)

And while there may be a workaround in regard to mocking data, there are still plenty of cases where I might need or want to use a protocol in my observable objects.

  • What my feeds come from different sources?
  • What if my feeds require different APIs?
  • What if my feeds are based on different protocols? (Atom, etc.)
  • What if my some of my feeds are cached?

In all of the above cases, it’s almost essential to have all of those different sources conform to a single protocol for better consumption.

Last and not least. This is simply better OOP.

Object-oriented programming has gotten a bit of a bad rap recently, and part of the problem stems from too much reliance on traditional object-oriented design practices.

In fact, the original problem is a direct consequence of the old Is-a vs. Has-a design question:

  • Is my object best described as being an instance of an A?
  • Or would I be better off if my object contained an instance of an A?

In this case, containing an instance of my protocol solved my problem and is also a better case of OOP design.

A win-win.

All right. I admit it. I exaggerated. SwiftUI isn’t killing Protocol-Oriented Programming.

But as demonstrated, it can be extremely difficult to use Protocol-Oriented Programming in SwiftUI observable objects unless you approach things with the proper mindset and unless you’re armed with the proper techniques.

In this case, just call me the arms merchant.

Til next time.

Read the entire SwiftUI Series

The Startup

Medium's largest active publication, followed by +773K people. Follow to join our community.

Michael Long

Written by

Michael Long is a Senior Lead iOS engineer at CRi Solutions, a leader in cutting edge iOS, Android, and mobile corporate and financial applications.

The Startup

Medium's largest active publication, followed by +773K people. Follow to join our community.

Michael Long

Written by

Michael Long is a Senior Lead iOS engineer at CRi Solutions, a leader in cutting edge iOS, Android, and mobile corporate and financial applications.

The Startup

Medium's largest active publication, followed by +773K people. Follow to join our community.

Medium is an open platform where 170 million readers come to find insightful and dynamic thinking. Here, expert and undiscovered voices alike dive into the heart of any topic and bring new ideas to the surface. Learn more

Follow the writers, publications, and topics that matter to you, and you’ll see them on your homepage and in your inbox. Explore

If you have a story to tell, knowledge to share, or a perspective to offer — welcome home. It’s easy and free to post your thinking on any topic. Write on Medium

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store