I wrote about coordinators at the beginning of the year, but the idea has matured a lot since then, and I’d like to reintroduce the topic with all of my learnings from the last few months.

This is adapted from a talk I gave at NSSpain this year. You can find the slides here. You can find the video here.

Three Problems

Overstuffed App Delegates

Apple does a really poor job of guiding us into putting code in good places. It’s really up to us to figure out how to structure our apps. The first place this is apparent is the app’s delegate.

The app delegate is the entry point into any app. Its primary responsibility is shuttling messages back and forth from the operating system to the app’s subsystems. Unfortunately, because of its position at the center of everything, it’s extremely easy to just plop stuff in here. One casualty of this strategy is the root view controller’s configuration. If you have a tab bar controller as the root of your app, you have to set up all the tab bar controller’s children somewhere, and the app delegate is as good a place as any.

In the first app that I made (and I suspect this is true for many of my readers as well), I put all the set up for my root view controller right in my app delegate. That code doesn’t really belong here, and it’s only there out of convenience.

I realized that after I made my first app, and I grew wiser. I did this trick:

@interface SKTabBarController : UITabBarController  

I would create a subclass of the root view controller I wanted to use, and I would tuck my code away in there. It was a temporary patch over the problem, but ultimately, it’s not the right place for this code either. I would propose that we examine this object, the root view controller, from a perspective of responsiblities. Managing the child view controllers is within those duties, but allocating and configuration them not as much. We’re subclassing a thing that was never intended to subclassed, just so we can hide away some code that doesn’t have a home.

We need a better home for this app configuration logic.

Too Many Responsibilities

Here’s another confounding problem. In the same way that its easy to dump tons of responsibilities into the app delegate, each individual view controller also suffers.

A small selection of the stuff we have view controllers do:

  1. Model-View Binding
  2. Subview Allocation
  3. Data Fetching
  4. Layout
  5. Data Transformation
  6. Navigation Flow
  7. User Input
  8. Model Mutation
  9. and many more besides

I came up with ways to tuck away these responsiblities in the children of each view controller in 8 Patterns to Help You Destroy Massive View Controller. All of these responsiblities can’t be in one place. It’s how we end up with 3000 line view controllers.

Which of this stuff should actually be in this class? Which should be elsewhere? What is the view controller’s job? It’s not clear.

There’s a great quote by Graham Lee that I love.

When you get overly attached to MVC, then you look at every class you create and ask the question “is this a model, a view, or a controller?”. Because this question makes no sense, the answer doesn’t either: anything that isn’t evidently data or evidently graphics gets put into the amorphous “controller” collection, which eventually sucks your entire codebase into its innards like a black hole collapsing under its own weight.

What the hell is a view controller? Controllers in the Smalltalkian sense were originally intended strictly for user input. And even the word “control” screws us. As I’ve written before:

When you call something a Controller, it absolves you of the need to separate your concerns. Nothing is out of scope, since its purpose is to control things. Your code quickly devolves into a procedure, reaching deep into other objects to query their state and manipulate them from afar. Boundless, it begins absorbing responsibilities.

Which of those responsibilities should be at the “controller level”? Graham’s right, the question doesn’t even make sense. Because we’re stuck with this awful word, we have to be really careful with what we allow our view controllers to do. If we don’t, it’s black hole central.

We also need help with this.

Smooth Flow

The last problem I want to discuss is navigation flow.

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {  
	id object = [self.dataSource objectAtIndexPath:indexPath];  
	SKDetailViewController *detailViewController = [[SKDetailViewController alloc] initWithDetailObject:object];  
	[self.navigationController pushViewController:detailViewController animated:YES];  
}  

This is a pretty common snippet of code, and I believe it’s in the Apple templates as well. Unfortunately, it’s garbage. Let’s go through it line-by-line:

id object = [self.dataSource objectAtIndexPath:indexPath];  

This first line is fine. The dataSource is a logical child of the view controller, and we’re asking it for the object we need to refer to.

SKDetailViewController *detailViewController = [[SKDetailViewController alloc] initWithDetailObject:object];  

This is where things start getting a little hairy. The view controller is instantiating a new view controller, the next one in the chain, and configuring it. The view controller “knows” what’s coming up next in the flow. It knows how that thing is to be configured. The view controller that’s doing the presenting knows a ton of detail about where it exists in the world of your app.

[self.navigationController pushViewController:detailViewController animated:YES];  

The third line is where it totally goes off the rails. The view controller is now grabbing its parent, because remember, these view controllers exist in a hierarchy, and then it’s sending a precise message to its parent about what to do. It’s bossing its parent around. In real life, children should never boss their parents around. In programming, I would argue children shouldn’t even know who their parents are!

In 8 Patterns to Help You Destroy Massive View Controller, I suggested a Navigator type that can be injected into view controllers that contains the logic for moving through your app. Navigators are a fine solution if you need it in one place, but we quickly run into an issue that navigators can’t help us with.

Those three lines have a lot of responsibilities in it, but that one view controller isn’t the only place that this is happening. Imagine you have a photo editing app.

Your PhotoSelectionViewController presents your StraighteningViewController which presents your FilteringViewController which presents your CaptioningViewController. Your navigation flow is now spread among three different objects. Further, something is presenting your PhotoSelectionViewController, but the dismissal has to be handled in CaptioningViewController.

Passing a Navigator around keeps these view controllers all coupled together in a chain, and doesn’t really solve the problem of each view controller knowing about the next one in the chain.

We also need help solving this problem.

Libraries vs Frameworks

I think Apple expects us to write code in all these ways. They want us to make the view controller the center of the world, because apps written all in the same style let them make the most impact with their SDK changes. Unfortunately, for developers, that’s not always the best move for us. We’re the ones who are responsible for maintaining our apps into the future, and dependable design and malleability of code are much higher priorities for us.

They say the distinction between libraries and frameworks is that you call libraries, and frameworks call you. I want to treat 3rd-party dependencies as much like libraries as possible.

When using UIKit, you’re not in charge. You call -pushViewController:animated: and it does a bunch of work and at some indeterminate time in the future, it calls -viewDidLoad: on the next view controller, where you can do some more stuff. Rather than let UIKit decide when your code runs, you should get out of UIKit -land as soon as possible, so you can have full control over how your code flows.

I used to think of view controllers as the highest level thing in app, the things that know how to run the whole show. But I started wondering what it might look like to flip that around. A view is transparent to its view controller. It’s bossed around by its view controller. What if we made the view controller just another thing that’s transparent in the same way?

Coordinators

What is a coordinator?

So what is a coordinator? A coordinator is an object that bosses one or more view controllers around. Taking all of the driving logic out of your view controllers, and moving that stuff one layer up is gonna make your life a lot more awesome.

It all starts from the app coordinator. The app coordinator solves the problem of the overstuffed app delegate. The app delegate can hold on to the app coordinator and start it up. The app coordinator will set up the primary view controller for the app. You can find this pattern in the literature, in books like Patterns of Enterprise Application Architecture. They call it the Application Controller. The app coordinator is a special version of an application controller, specifically made for iOS. The app coordinator can create and configure view controllers, or it can spawn new child coordinators to perform subtasks.

Which responsiblities do coordinators take over from the view controller? Primarily, navigation and model mutation. (By model mutation I mean saving the user’s changes to the database, or making a PUT or POST request to an API, anything that can destructively modify the user’s data.)

When we take those tasks out of a view controller, we end up with a view controller that’s inert. It can be presented, it can fetch data, transform it for presentation, display it, but it crucially can’t alter it. We know now that any time we present a view controller, it won’t take things into its own hands. Whenever it needs to let us know about an event or user input, it uses a delegate method. Let’s take a look at a code example.

Code Example

Let’s start from the app delegate.

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {  
	self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];  
	self.rootViewController = [[UINavigationController alloc] init];
	
	self.appCoordinator = [[SKAppCoordinator alloc] initWithNavigationController:self.rootViewController];  
	[self.appCoordinator start];
	
	[self.window makeKeyAndVisible];  
}

The app delegate sets up the apps window and root view controller, and then fires up the app coordinator. The coordinator’s initialization is separated from starting its work. This lets us create it however we want (lazily, greedily, etc), and only start it when we’re ready.

The coordinator is just a simple NSObject :

@interface SKAppCoordinator : NSObject  

This is great. There’s no secrets going on here. The UIViewController class is thousands of lines, and we don’t know what exactly will happen when we call any of its methods, because it’s closed-source. Making the objects that run our app simple NSObject types makes everything simpler.

The app coordinator initialized with the data it needs, which includes the root view controller.

- (instancetype)initWithNavigationController:(UINavigationController *)navigationController {  
	self = [super init];  
	if (!self) return nil;
	
	_navigationController = navigationController;
	
	return self;  
}  

Once we hit the -start method, the coordinator sets off to work.

- (void)start {  
	if ([self isLoggedIn]) {  
		[self showContent];  
	} else {  
		[self showAuthentication];  
	}  
}  

Right from the start, coordinator is making decisions. Previously, logic like this didn’t have a home. You could maybe stick it in a view controller or in the app delegate, but those both have their flaws. In a view controller, you have one view controller which is making some decisions well-beyond its purpose, or you’re polluting the app delegate with stuff it doesn’t care about.

Let’s examine the -showAuthentication method. Here, our base coordinator spawns off child coordinators to do work and subtasks.

- (void)showAuthentication {  
	SKAuthenticationCoordinator *authCoordinator = [[SKKAuthenticationCoordinator alloc] initWithNavigationViewController:self.navigationController];  
	authCoordinator.delegate = self;  
	[authCoordinator start];  
	[self.childCoordinators addObject:authCoordinator];  
}  

We use an array of childCoordinators to prevent the child coordinators from getting deallocated.

View controllers exist in a tree, and each view controller has a view. Views exist in a tree of subviews, and each subview has a layer. Layers exist in a tree. Because of this childCoordinators array, you get a tree of coordinators.

This child coordinator will create some view controllers, wait on them to do work, and signal us when it’s done. When a coordinator signals that its finished, it cleans itself up, popping off whatever view controllers it has added, and then uses delegates to get messages back up to its parent.

Once we’ve authenticated, we’ll get a delegate message, and, we can allow the child coordinator to be deallocated, and then we can get back to our regularly-scheduled programming.

- (void)coordinatorDidAuthenticate:(SKAuthenticationCoordinator *)coordinator {  
	[self.childCoordinators removeObject:coordinator];  
	[self showContent];  
}  

Inside the authentication coordinator, it creates whatever view controllers it needs, pushes them onto the navigation controller. Let’s take a look at what that looks like.

@implementation AuthCoordinator

- (instancetype)initWithNavigationController:(UINavigationController *)navigationController {  
	self = [super init];  
	if (!self) return nil;
	
	_navigationController = navigationController;
	
	return self;  
}  

Initialization is similar to the app coordinator.

- (void)start {  
	SKFirstRunViewController *firstRunViewcontroller = [SKFirstRunViewController new];  
	firstRunViewcontroller.delegate = self;  
	[self.navigationController pushViewController:firstRunViewcontroller animated:NO];  
}  

Authentication needs to start with a “first run view controller”. This view controller has buttons for sign up and log in and maybe a little slideshow explaining the app. Let’s push that view controller on and become its delegate.

This view controller has a delegate so we can be informed when the user taps the “sign up” button. Instead of the view controller needing to know which signup view controller to create and present, the coordinator will handle that.

- (void)firstRunViewControllerDidTapSignup:(SKFirstRunViewController *)firstRunViewController {  
	SKSignUpViewController *signUpViewController = [[SKSignUpViewController alloc] init];  
	signupViewController.delegate = self;  
	[self.navigationController pushViewController:signupViewController animated:YES];  
}  

We become the sign up view controller’s delegate so that it can inform us when its buttons are pushed.

- (void)signUpViewController:(SKSignUpViewController *)signupViewController didTapSignupWithEmail:(NSString *)email password:(NSString *)password {  
	//...  
}  

And so on. Here we actually perform the work of the signup API request and saving the authentication token, and then we inform our parent coordinator.

Whenever anything happens with a view controller (like user input) the view controller will tell its delegate (in this case the coordinator) and the coordinator will execute the actual task that the user intended. It’s important to have the coordinator do the work, so that the view controller remains inert.

Why are coordinators great?

  1. Each view controller is now isolated.

    View controllers don’t know anything beyond how to present their data. Whenever anything happens, it tells its delegate, but of course it doesn’t know who its delegate is.

    Before, when there was a fork in the road, the view controller needs to ask “Okay, well, am I on the iPad or the iPhone?”. “Is the user being A/B tested?” They no longer have to ask any questions like that. Have we merely pushed this question, this conditional, up to the coordinator? In a way, but we can solve it in a much better way up there.

    When we do need to have two flows at once, for A/B testing or multiple size classes, you can just swap the entire coordinator object instead of sticking a bunch of conditionals all over your view controllers.

    If you want to understand the way a flow works, that’s now super easy, since all the code is in one place.

  2. View controllers are now reusable.

    They don’t assume anything about what context they’ll be presented in, or what their buttons will be used for. They can be used and reused for their good looks, without dragging any logic along with them.

    If you’re writing your iPad version of your app, the only thing you need to replace are your coordinators, and you can reuse all the view controllers.

  3. Every task and sub-task in your app now has a dedicated way of being encapsulated.

    Even if the task works across multiple view controllers, it’s encapsulated. If your iPad version reuses some of those subtasks but not others, it’s really easy to use just those sub-tasks.

  4. Coordinators separate display-binding from side effects.

    You never again have to worry about if a view controller will mess up your data when you present a view controller. It can only read and display, never write or corrupt data. This is a similar concept to command-query separation.

  5. Coordinators are objects fully in your control.

    You’re not sitting around waiting for -viewDidLoad to get called so you can do work, you’re totally in control of the show. There’s no invisible code in a UIViewController superclass that is doing some magic that you don’t understand. Instead of being called, you start doing the calling.

    Flipping this model makes it much easier to understand what’s going on. The behavior of your app is a completely transparent to you, and UIKit is now just a library that you call when you want to use it.

The Backchannel SDK uses this pattern to manage all of its view controllers. The app coordinator and auth coordinator examples come from that project.

Ultimately, coordinators are just an organizational pattern. There’s no library you can use for coordinators because they’re so simple. There’s no pod you can install and nothing to subclass from. There’s not even really a protocol to conform to. Rather than being a weakness, this is a strength of using a pattern like coordinators: it’s just your code, with no dependencies.

They’ll help make your app and your code more manageable. View controllers will be more reusable, and growing your app will be easier than ever.