Skip to content

Generic datasources and cells. Introducing FountainKit.

A couple of months back, I published a couple of posts that seemed to rise some interest (for this blog’s standards), about unit testing collection and table view cells, and leveraging generics to implement collection view datasources.

Huge, monolithic view controllers are not good for any codebase

A view controller, like any other class, should have only one responsibility. However, often times, view controllers do too much: layout views, deal with user interaction, load data from the network, and prepare data to be presented by table views and collection views.

Let’s take a closer look at that last responsibility: preparing data to be presented by a table or collection view. That usually happens by means of implementing either UITableViewDataSource of UICollectionViewDataSource. Just to simplify the discussion, let’s say that often times, view controllers behave as datasources.

The problem with that is that we are adding a responsibility to an object that is already doing too much. In fact, it is not just responsibility, but multiple ones: the view controller would need to be aware of the structure of the data that we want it to present, it will have to be aware of how we want to present it (since we usually populate public cell outlets in the implementation of the cellForItemAtIndexPath method), and since it is already aware of the data structure, it is very likely that it will also handle user interaction with the table/collection, and therefore it will also be responsible for navigating to another view controller, or performing network request.

That’s too much. Those are too many reason why the code in that view controller might need to be changed, those are too many things to get right in just one object. And code that does too much is error prone and difficult to maintain.

So, now that we have established that we don’t want view controllers doing too much, and in particular that we don’t want view controllers acting as data sources, what can we do to avoid that scenario?

Divide et impera I: Data

As usual, the solution is breaking the problem down into smaller pieces. Highly cohesive pieces.

We need to fulfil three responsibilities: manage a data collection, prepare cells according to the way we want to represent the data in that collection, and render that information on screen. So, we have three distinct responsibilities, but somehow related by the kind of data we want to present on screen.

First order of business would be encapsulating the management of the data collection. We want to do it in a way that allows us to manage strictly-typed data, without knowing exactly the type of our data. Well, that’s what generics are for, don’t you think?

But we also want to be able to manage different data structures in an abstract way. Well, that’s what an interface (protocol) is for, don’t you think? 😏

So, this would be our starting point.

Now, we can have an implementation of that protocol that manages a single section data structure (AKA flat array)

Divide et impera II: Cells

Cells are the only entities that should be involved in actually rendering information on screen. If we think of data in one end of the pipeline, cells would be in the other end.

Cells are pure UI. Actually, me might see them as tiny little view controllers, with one responsibility: populate outlets (i.e. labels and image views) with data, doing that in the simplest possible way (that means, directly assigning values, without being concerned by any other business logic)

Again, have in mind that we are sending a data object (model object) through and imaginary pipeline that starts in our data structure. And, have also in mind that, as usual, we want to be able to provide different cells, all of them sharing the same behaviour (being able to accept a data object of a generic type)

In order to achieve that, we will make our cells implement this protocol:

Divide et impera III: Binding data and cells

Well, that’s what data sources are for, aren’t they?

A datasource is nothing more that the entity that is in a position to know which cell needs to be presented on screen, grab the cell and the data object that needs to be presented, and force them to talk to each other.

If we think about this whole thing as a pipeline with the data model in one end and the cells in the other, the datasource would be the actual pipeline, it would be the glue that binds data and presentation together.

Let’s declare a class that implements UITableViewDataSource. We are only going to implement the required methods of UITableViewDataSource, so we will not make this class final. If, eventually, in an specific project, we need to implement more methods of the data source protocol, we can subclass.

Also, we want to bind a data collection of type T with a cell that implements a var of type T (remember, we want to be able to provide the model object to the cell).

So, this would be the declaration of our datasource class:

Notice the generic constraints: we want this class to be qualified by two generic types, but we want the first of those types to implement DataManager, the protocol that abstracts our data collection, we want the second type to implement DataSettable, the protocol that abstracts our cells, we want the second type to be a subclass of UITableViewCell, and here is the tricky pat, we want the associated type declared in the DataManager protocol to be equal to the associated type declared in the DataSettable protocol.

Now, that’s all good, but we need one extra step in order to make all the pieces fit together. In the data source initialiser, we declare two parameters, the data manager, and the cell type. Notice how we do not retain the cell type, we only pass that parameter to make sure that the generic constraints are enforced.

Divide et impera IV: The benefits of dividing, aka conquer.

It could be argued that all of this is nothing more that a gymnastics exercise on generics. But there are some significant benefits to it:

  • Better testability. This approach makes the cells, the view controllers and the datasources easier to test.
  • Better separation of concerns. This one goes hand to hand with the previous point. When there is good separation of concerns, things are easier to test.
  • Higher cohesion, and lower coupling. We broke the problem into smaller, highly cohesive pieces. Each piece has one and only one responsibility.

Introducing FountainKit

I have been using this approach in my projects for quite some time now. It works well for me, and I thought that it might work well for others.

So I have packed some of my code into a framework, and published it to Github, including a sample project that shows how to implement an actual table view:

https://github.com/ctarda/FountainKit

There are plenty of improvements to make, but there is nothing like receiving feedback (Agile mindset FTW!) to build software that solves actual problems.

2 Comments

Leave a Reply

Your email address will not be published. Required fields are marked *