Coordinate, don’t Delegate

Adam Stener
9 min readJan 28, 2019

--

When building apps or writing software in general, one of the main concerns a developer strives to solve is that of coupling. Two of the most common methods to achieve decoupling are the Single Responsibility Principle (SRP) or Separation Of Concerns (SOC). Over the years our industry has adopted many different approaches to work on this issue.

MVC

Apple has given some guidance in their documentation on how to separate concerns within an application and they repeatedly refer to the Model View Controller architecture.

The general idea is that there are 3 buckets of concerns that a feature in your application can use to separate out its functionality, and that when you think about how to layout your code you can think of the concerns in terms of Models, Controllers, or Views. There is even some guidance on how these buckets should interact with one another. A Model and a View should not communicate with each other directly, but through a Controller. This idea is fantastic and has been utilized for many years. Reducing this idea to its core, what’s happening is we’re being prescribed a high level idea of where different groupings our code can fall into and how they should communicate with each other. While the strategy of breaking up concerns of your app and determining how they should be encapsulated is sound, it turns out to be unrealistic to have all of your application’s concerns represented in these 3 ideas. Since a View and a Model tend to be straightforward to distill into a primary concern, it’s the Controller that ends up being a dumping ground for everything else that has led to the overloaded term of MVC to also mean Massive View Controller.

Here is an example Controller, with its individual areas of concern highlighted in different colors for easy visualization.

So how do we solve this multi-colored object and its lack of focus?

Coordinator

In 2015 (thanks Soroush!) the idea that logic pertaining to anything other than controlling views should be pulled out of a Controller and put somewhere else was introduced. The proposed “somewhere else” was the Coordinator object. This light weight object would sit atop the Controller and allow it to delegate responsibility that lies outside of specifically controlling and managing views. This idea is fantastic and was the first time that I was introduced to a discussion around how to make UIKit work for you instead of trying to conform to its ideals. Now we have a new place to toss code that would otherwise interfere with the primary concern of a Controller. Yet while we trimmed down the responsibility of what a Controller does we still have a fairly generic bucket called a Coordinator that in many implementations has become the new dumping ground of multiple functionalities. Looking at the color of responsibilities, visualized with a Coordinator included, it becomes apparent that the Controller is much more focused but we still have a fairly generic Coordinator object that could be responsible for too many things.

There is no single adopted idea of exactly what a Coordinator should be concerned with. In many cases the Coordinator has collected many responsibilites such as interacting with Networking, Routing, Persistence and others. In other cases it is simply used as a conduit for passing messages to those Managers, Services, Providers (choose your flavor of the year). They are also generally coupled to a controller through a protocol. While two objects communicating through a protocol isn’t technically coupling them, more often than not (or maybe more often than we’d like to admit) the protocols along these communcation channels inevitably turn into large specific contracts that might as well render your objects coupled. I should call out here that I believe this was not the original intent of the Coordinator, and many projects may not extract all these responsibilites into one. But if these concerns are in the Coordinator or in the Controller, through injectible references or not, the idea remains that the Controller/Coordinator combined are frequently responsible for (or involved in) too many things.

I would argue that what has been achieved by adding a Coordinator is more separation of concern, but not total separation of concern and the boilerplate is mounting. We’ve added another bucket (or letter in our architecture…MVVM, MVP, VIPER) to our list of places where we can push our concerns. The controller can now be more like a Model or View, and have a focused concern, but we’ve also added to the amount of code and testing it takes to manage this flow by introducing a protocol and coupling more layers. By adding these layers, the responsibilities of each component are slowly reduced, but we are left with a hierarchy of objects who all need the extra delegation code to know how to talk to one another. While the responsibility count of each item are dwindling, that the amount of delegation code needed to manage the communication between these items has become daunting. (And are these delegate protocols really even delegates most of the time?).

Maybe a good litmus test is to ask yourself how often you pause before a new implementation and think “That’s a lot of layers I have to work through to get the data where I need it”.

What sounds really nice is a better way to take areas of concern, model them in a container of sorts, and introduce them into the flow of an app fully encapsulated. Let’s start with one of the more difficult areas to achieve that because it’s so heavily integrated with the iOS SDK.

The View Stack

What if we could remove all the business logic, including Routing, Networking, Persistence etc from the entire view stack, and make our UI completely stupid at every layer? A hierarchy without Coordinator cornucopia. No delegates to push some random responsibility to and no ManagerHelperServiceProtocols to agonize over naming, hoping your coworkers don’t roll their eyes when they see one. It would be just a pile of Views and Controllers. We’ll need a single Coordinator atop this mountain to coordinate the entire stack, but everything underneath will be vanilla UIKit.

How would these layers be able to communicate with one another when events happened in leaf views, that should trigger some business logic or potentially alter the state of our UI stack?

UIResponder

Apple uses a UIResponder chain to communicate UIEvents that are happening in leaf views up to a parent View or Controller that might care about that particular UIEvent. This idea sounds very close to the simple communication path we all desire. The UIResponder chain uses a property called ‘next’ to determine who is next in the chain of responsibility to receive these UIEvents. If we add a protocol with a variable to the UIResponder that simply returns this value, we can ride this train up to the point that we want and then transfer to our UI Coordinator.

Dispatching an event with data becomes trivial, with only a few lines of code required. Because we implemented this with a protocol, we can also adhere our Coordinators so they can take advantage of the same responder chain.

Now anything in the view stack or Coordinator chain can emit an Event that will automatically make its way up to the top level Coordinator whose sole responsibility is to Coordinate Events between its children. In the example above, I left the event object as ‘Any’, but this could be a protocol, or a specific type if you wanted to use a single enum. The implementation here would vary for every team and project.

An Event being processed in the ApplicationCoordinator can talk to any number of functional areas that represent a single concern. For example a networking API, a Core Data stack, or a Router, where each one has a specific purpose and flow, all managed by a single ApplicationCoordinator.

Just like Docker has Containers, your ApplicationCoordinator will have children with a focused or contained responsibility. Adding a new bucket becomes easy to ‘dock’ in our ApplicationCoordinator, quickly adding a new encapsulated area of concern that has the capability of completely isolating itself from other responsibilities. What we are left with is very few Coordinators, a rare delegate, no extra cruft to worry about and very little code to manage to communicate between the sections of our app. Our single responsibility score has increased dramatically, and we even have a prescribed way to add new concerns to our app without polluting what we have already built. These changes are also done with protocols which means we still maintain our test-ability with dependency injection, or responder interception.

So What?

Big deal. We’ve separated some concerns and provided a means for code to live in its own space and only be concerned with itself. We’ve also made the idea of adding new concerns scalable but we’ve lost the ability (or baggage) of having strong contracts between many objects. This will probably increase how much functional testing and integration testing that needs to be done, which is arguably better for the long run. This is probably the hardest part of the concept to come to grips with. It would mean letting go of the layers of indirection, and endless heaps of mocked unit tests, so we can focus on the interactions between responsibilities. We also haven’t reviewed some of the other large wins that moving this direction could afford.

State Management

I’m a big fan of Controlling Your World (thanks Stephen Celis!) but one of the problems with global mutable state is safety. If anyone can access and mutate state at any point, it’s not hard to see how that could turn into a mess. But in this proposed design, with Events all being managed in a single spot, we have the ability to model our global state as readable but privately mutatable. Now you can get the best of both worlds, and control them all. We have the opportunity to inject or read state from anywhere in our application. Utilizing Swift’s private(set) capabilities, we can design our state to only be mutated through the known, testable means of dispatching Events.

Event Capturing

With a single place for processing these Events, one thing that becomes trivial is capturing the entire list of Events that took place during a user session. If memory or privacy were not an issue, one could also store every state manipulation associated with the Event, for rich debugging experiences. In theory, if an app were built to be able to start with a list of preloaded Events, it would require little to no effort to start a session and execute a flow that reproduces a specific state or issue that you’re having. The debugging implications here are tremendous.

Final Thoughts

Encapsulating functionality is great and this goal is something that we almost all certainly share. I aimed to make the process as simple as possible by removing delegates and extra layers of management, but still maintain the ability to write solid tests. The things that you can do with your data once Events are centrally located are extremely powerful but losing the idea of a strong contract will no doubt be too far off the beaten path for most to adopt.

If you’re reading this and thinking about terms like Redux or Elm then you’re probably thinking about it all in the same way I am. The main idea here was just to apply the fairly loose idea of Coordinators into a specific design that could yield all these benefits. Because we can’t have an idea without a name these days, I’m referring to this thing or idea as Cedar, after the Elm naming convention. A Cedar Elm is a particular type that can be grown in urban areas as it can persist in conditions like pollution and poor soil. In this case, Cedar stands for Centralized Event Dispatching And Responding and it’s consistency with the Cedar Elm is uncanny.

I’d also like to give a shout out to radianttap whose similar ideas on utilizing the UIResponder were very helpful!

--

--