A few years ago, I wrote about a simple technique to encourage code reuse in data sources. I used this all the time in Objective-C, but I could never get it to quite feel right in Swift. I always had to resort to using type erasers or simply forgo strong types altogether.
Luckily for me, seeing Rob Napier’s talk at Swift by Midwest, then watching Brandon Williams’ talk from AppBuilders, I was finally armed with the knowledge I needed to get this stuff working.
In this article, I want to show you how I fixed this problem. We’ll look at:
- Replacing PATs (protocols with associated types) with structs
- Using extensions and generic constraints to cover your use cases
If you’ve been struggling to model something using protocols with associated types, or you simply want an easy way to build your data sources, this article is for you!
Where we started
I had gotten used to breaking up my data sources into 2 pieces: one that dealt with the providing data, and one that dealt with presentation.
This allowed me to reuse the “data provider” part across multiple applications, implementing only the bare minimum amount of code to get a new table view or collection view up and running. What’s more, this was a data source that acted like a data source. No crazy
UITableView subclasses, no special controllers. Just vanilla UIKit.
When translating this into Swift, I naturally turned to protocols. I ended up with something like this below.
This doesn’t look too complicated, but when you try to build an adapter class that actually uses these, things fall apart rather quickly. Since our two protocols have an associated type, there’s no way build a reusable adapter that holds references to these objects without using a type eraser.
And up until recently, I did just that: I used a type eraser in order to get this to work, but that was a bit cumbersome. It never really felt right. It forced me to write things like this below.
As I mentioned earlier, Rob’s talk at Swift by Midwest made me realize I was on the wrong path, but I didn’t feel like I knew how to fix it.
But then Rob tweeted about Brandon’s AppBuilders talk, and I knew I had to watch it.
This really made things click. Rob made me realize I didn’t want a protocol, and Brandon showed me how to tackle the problem in a systematic way.
De-protocolizing your PATs
Here’s how I got myself out of my strongly-type-erased hell.
Instead of futilely trying to get protocols to work, we can instead define a struct with the functions we need:
Here’s a quick illustration of how I moved from a protocol to a struct.
First of all, we can see that all the functions that were defined in the protocol are now properties of my struct.
Second, we notice that each function has an extra parameter. This parameter is the data we’ll be operating on. It replaces whatever implicit input we would be using from our conforming class.
Third, we notice that the type of this first parameter replaces the associated type from our protocol. Instead of dealing with a PAT, we now have a generic struct.
It’s great that we made it this far, but what does this struct mean? What is a data provider, aside from a bunch of function properties?
Here’s how I define it: it’s a type that knows how to separate a given
ItemStore into an ordered collection of
Item. It knows how to traverse
ItemStore using an
IndexPath to return an item
How do we actually use this thing?
Now that we have a data structure that makes sense, we can specialize it based on its generic parameters. This is where the technique really shines. Let’s say I want to define a data provider that can map an array to a single section. This is a common scenario, and it’s pretty easy to accomplish:
There are a few things going on above, so let’s unpack them:
- First of all, we’re using an extension with a generic type constraint to produce a default implementation. As long as
ItemStoreis an array of equatable
Item, we’ll have access to this extension.
- Second, we’re defining a
static varthat returns a new
DataProviderthat’s fully configured. We should be able to create a single section data provider in a single line of code.
Using generic type constraints, we can model this data provider rather succinctly.
Let’s try our hand at modelling a multi section data provider driven by an array of arrays:
Now that we have a few common cases handled, let’s look at the presentation side.
Table View Presentation
The data part we just looked at can be used with
UICollectionView, or even
UIPickerView, but the presentation half needs to be view specific. Let’s dig into that now.
Using the same technique as above, our struct would look something like this:
This one is a bit easier to manage, and defining it inline is simpler too:
Notice we’re simply using the default initializer on this one.
Tying it all together
Now that we have a provider implementation and a presenter implementation, we need to create our adapter class. This is the class that actually conforms to
Its implementation is rather simple: it forwards methods to the provider and the presenter in order to make things work. It’s generic over
Item, ensuring that the generic types between the
DataProvider and the
TableViewDataPresenter line up.
You’ll also notice that there’s a
Fetchable type. I’ve added this in my own implementation because I want to be able to fetch my data asynchronously if necessary. (Though that’s a bit beyond the scope of this article. If you want to learn about the pattern I was trying to achive, check out the first article from a few years ago.)
Using a data source
Finally, when you’re ready to create your data source, you end up with something like this:
And your table view controller’s
viewDidLoad can look something like this:
Isn’t that just beautiful? A vanilla data source, modular and strongly typed.
Is it worth the trouble?
Now that may have seemed like a lot of code for something relatively simple. I’m sure you’ve implemented a data source dozens of times.
And while that may be true, it’s worth noting that many of these pieces are reusable. Our concrete data providers, like
.multiSection, can be used again and again.
The adapters as well (
TableViewDataAdapter) are also reusable. For the vast majority of situations, you shouldn’t have to make any changes to them.
If you’re creating a new controller, the only piece you’ll have to reimplement is the “data presenter” part of this trio, which also happens to be the simplest piece of all.
All this to say that I find this approach lands right in the sweet spot of reusability. It doesn’t stray far enough from UIKit to seem completely foreign, it strikes a good balance between flexibility and modularity. The base components are useful in many situations, but if you need something special, you don’t need to be a rocket scentist to build what you need on your own.
Thanks again to Rob and Brandon for their amazing talks, and a friend at Big Fruit who helped me work through this problem. Talks are linked below:
I’ve landed somewhere I’m happy with, and I’ve created a gist of the implementation in case you’d like to see it for yourself.