Skip to content

Why to avoid tracking state in View Controllers

(And a hint on how to avoid it).

State is a biatch. It makes difficult to maintain code because it makes harder to reason about code.

But, if there is one place where tracking state is particularly bad, that would be, in my humble opinion, a view controller.

UIViewController is the centre of the iOS universe. Tooling, documentation, make easier to turn view controllers into huge, tangled entities with multiple responsibilities.

So in this post, I would like to focus on one specific thing that should not be responsibility of a view controller: tracking state.

State has many disguises

I have always found very difficult to define state in just one sentence. State can be many things. Is this view controller waiting for a process to complete? Is this view controller displaying a spinner? Is this view controller displaying data in edit mode? Is this view controller displaying an error message?

All those examples, and many more, are different manifestations of the same issue: a view controller is tracking state. And that’s a problem.

What’s wrong with a view controller tracking state?

A view controller, at least according to its name, should do just one thing: control a view. That, in my opinion, implies populating the controls within a view (in proper iOS-speak, that would be setting values in the view’s outlets), and passing user interaction commands to somebody else (in proper iOS speak, that would be receiving actions, and propagating them to whatever entity is in a position to deal with them).

The problem is that, as soon as we are not very strict in terms of what a view controller should or should not do, that same view controller ends up doing a little bit (or a whole lot) of everything, from networking to creating other view controllers and managing parts of the app’s navigation flow.

Navigation is something in particular that I believe should never be part of a view controller responsibility. But that requires a completely different post. In any case I recommend reading these two approaches to the problem.

But let’s pretend, just for today, that triggering navigation commands can be part of what a view controller should do.

An example

For the shake of keeping this post as short as possible, let’s consider the following example. We are working on an image editing app. We want to present a list of images to our users, in the form of a table view. When the user taps one of the table view’s rows, we transition to a photo editor.

Let’s assume, again for the shake of keeping the post short, that the view controller presenting that list of images implements UITableViewDelegate. (hint: most likely it should not implement it, but that’s a matter for an upcoming post).

So, when the user taps a row, this is what would happen.

Fair enough. We just built the simplest thing that could possibly work. Self high-five! (but again, a view controller should not trigger navigation)

Requirements change

If there is one thing we can be certain about in our craft/profession is that requirements will change.

Continuing with our example, our app has been a success, and users are demanding new features left and right. One of the most demanded features is being able to transfer images to other users. So, we want to display a list of images (again, in a table view), but now we want to launch the native sharing sheet when the user taps one picture.

We obviously want to reuse as much as possible of the implementation of the photo list that we already have, so a possible solution could be providing the view controller with some information about what it was launched for, so it can decide how to behave when the user selects an image.

So, just to recap, we want to make our view controller aware of the reason why it was launched, so it can behave according to that reason. In other words, we want to execute different actions, according to the view controller state, as a response to the same user interaction.

The simplest solution

The simplest thing that could possibly work might be something like the following:

We are letting know the view controller of its reason to be presented when we create it, and then, make it execute a different command according to that very same reason.

So, we can launch our image list like this:

There be dragons

This solution seems perfectly valid, but there be dragons.

First, we are breaking the Open Closed Principle. This principle says, more or less, that “modules should be open for extension but close for modification”. How are we breaking it?

Well, imagine that a new requirement comes in: now we need to display also a list of images, but this time, when the user selects one, we want to delete it. That would imply adding a new case to the enumeration, and a new branch to the if in the didSelect method. So, in order to extend the module (add new functionality) we need to modify the module (add new code).

Secondly, we have a more subtle issue here. We want our view controller to do different things according to its state. We want different behaviours according to different states. The problem with that, is that it is very difficult to contain that. Once you correlate your class behaviour to its state, that correlation breeds and spreads everywhere. Sometimes slowly, some times faster, but it spreads.

That correlation manifests itself in the form of if-else clauses, in particular in the form of complex if-else clauses, where you need to check the value of instance variables (properties) in order to know what your code should do.

In a nutshell, by coupling behaviour to state, you introduce complexity. Complexity in your code, complexity in the logic that your code models. And complexity is the source of most bugs: when something is complex it is more difficult to reason about it, and it is easier to oversee subtleties, to miss logical paths, to make mistakes.

So, if state is the source of most complexity, and complexity is the source of most bugs, then removing state sounds like a great idea. But, how can you remove state?

Isolate the source of your problems. Then remove it.

So far, we have stated how, in the context of view controllers, state and behaviour are a source of problems. So we could start by isolating them, and after that, removing them from the view controller.

What if we could write our didSelect method like this?

That would avoid all the issues previously discussed. The didSelect method wouldn’t need to know anything about context or internal state. It couldn’t be simpler, just two lines!

Notice also how we don’t need to track state any more. We can get rid of the property of type Destination, and of the actual Destination enumeration.

But, what is exactly ImageSelection and how would it solve the problem?

It’s all about trust

We try to develop software by modelling abstractions, usually in a way that those abstractions model behaviours.

The way I understand OOD, wearing the software architect hat is kind of being the god of your own world, a world where you create entities with specific behaviours (objects), and declare some rules about how those entities can collaborate with each other (interfaces). That implies that, if object (entity) A knows that object (entity) B provides a given collaboration rule (interface), then object A should trust object B to fulfil his duties without actually knowing what those duties are.

In our example, the view controller will tell ImageSelection to do whatever ImageSelection considers necessary to fulfil its duties.

But, remember, we need ImageSelection to have different behaviours, according to the context where the view controller is created.

So, we have a collaboration rule (the ImageSelection api), and we need different implementations of that collaboration rule. And that is called…

Polymorphism

In iOS speak, that means that ImageSelection is a protocol. And that we can have different implementations of that protocol providing different behaviours that still comply with the protocol contract (the aforementioned collaboration rules).

Let’s begin with the protocol:

Let’s say that one implementation of the ImageSelection protocol corresponds to the behaviour necessary to transition to a photo editor. It could look like this.

We could have another implementation of the ImageSelection protocol contains the logic necessary to launch the sharing sheet.

Now all we need is a way to provide an specific implementation of the ImageSelection protocol to our view controller. So, say hello to another of the SOLID principles.

Dependency inversion (and dependency injection)

The Dependency Inversion Principle says more or less “you should rely on abstractions, not concretion”. Well, if you look at the current status of the didSelect method, that is actually what is happening. That methods collaborates with an abstraction (protocol) not a concretion (instance of a class). So, we are good.

But how do we provide that abstraction to the view controller? Through dependency injection. In this case, passing the concrete implementation of the ImageSelection protocol to the view controller in its initialiser.

When we need to present the image list for users to edit a photo, we just need to provide the proper implementation of the ImageSelection protocol.

And the same when we need to present the image list to share a photo.

Final words

Let’s take look at the example we have been discussing again, trying to focus on the big picture. This is what we did:

  • Isolate whatever parts of the view controller are related to state.
  • Remove all traces of state from the view controller.
  • Abstract whatever different behaviours we might have behind an interface, and provide specific implementation of that interface for each of those behaviours.
  • Inject the behaviour, according to the context where the view controller is created.
  • Trust the collaboration contract.

That way, we achieved:

  • A cleaner view controller:
    • It doesn’t do anything it shouldn’t, according to what a view controller should be, like keeping track of state or contain logic related to state.
    • It contains simpler code, easier to understand and easier to reason about.
    • It is open for extension and closed for modification. If there is a need to display the same view controller with a different behaviour associated to it, it is just a matter of providing the view controller with a reference to a third implementation of the ImageSelection interface.
    • Testability is improved. The behaviour of the view controller when an image is selected can be mocked.
  • A set of classes, each modelling one single behaviour. Each of those classes, will be:
    • Highly cohesive (it does just one thing)
    • Loosely coupled from the rest of the system.
    • Highly testable. Each one of those classes needs to fulfil just one expectation
    • Reusable. Each implementation of the ImageSelection protocol encapsulates a behaviour, not a thing. Behaviours are easier to reuse than things. A specific example: the Photo Editor view controller might want to allow users to share the edited photo by launching the sharing sheet.
  • Cleaner, more declarative code. (I wrote three other posts about declarative vs imperative code)
  • We open the possibility of declaring a default implementation of the ImageSelection protocol in an extension, and override that default implementation only if and when necessary. POO FTW!

There are other approaches, of course. The first one that comes to mind mind would be relying on extension, instead of composition. But that would be a matter for another post. One problem at a time.

3 Comments

  1. […] discussed a similar issue in this post, focusing on state, but the case where this smell is a symptom of lack of abstraction was […]

  2. George Yang George Yang

    Thanks for writing this post and sharing it!

    First, there are couple typos, I think you meant “sake” not “shake”.

    Second, in the classes that adopt ImageSelection, how do you get a reference of the “navigationController”? Through “inViewController”?

    Also, where does “selectedPhoto” come from? Or are you actually referring to the passed-in parameter “photo”?

    • sexysoftwareengineer sexysoftwareengineer

      Hi George.

      Thanks for pointing out the typos, I’ll fix them asap.

      Regarding the navigationController, I’m assuming that class is embedded in one, for the sake of the example 😉

      And yes, I’m referring to the passed-in param. I’ll update the code asap

Leave a Reply

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