Coordinators: Reexamination

Taming Unruly Application Architecture

Back To Coordinators

So it has been a few months since I wrote my previous post on coordinators. In the interim, a lot has happened! I published a new application called PodCatch (which I built using the coordinator concept — still work in progress):

And I attended/volunteered at the try! Swift Conference in New York City:

Me

Along the way I’ve had a chance to explore the concept in greater detail and expirement to find what features have worked for me.

Circling On Back

I feel like now is a good time to circle back and finish up my series on coordinators. Before I get too far, I want to own up to something first. I don’t think my first post was a great introduction to the topic. It lacks focus since I tried to introduce too many concepts into something that is much more narrow in scope. In addition to this post, there will most likely be a part three to this series, because this post isn’t complete in and of itself and I’m not happy with what I originally wrote. I know circling back now is a bit of a non-sequitur from the series I’ve been writing on CoreLocation as well as the example project I hope to have out (next week tentative). I’m writing this now because I want to get it down while the conversations I had on the topic at the try! Swift conference in NYC this past week are still fresh in my mind. ¯\_(ツ)_/¯

The Application Controller/ Coordinators

So let’s do a bit of review from part one, if you’ve already read that post, this might seem a bit redundant. One of the problems with view controllers is that they can become massive and unwieldy as your application development progresses. This is because their purpose can be interpreted broadly. If most of your application works within views and a view controllers ‘controls’ views then it seems perfectly reasonable to place much of your application logic within it. Obviously there are serious drawbacks to this approach. If you’ve read Soroush Khanlou’s blog posts on coordinators then this is something extra, if you haven’t I’m just briefly going to touch on some background information.

Unfortunately, a view controller-centric approach to app development isn’t scalable. The Coordinator is a great way to isolate your code into small, easily-replaced chunks, and another part of the solution to view controller malaise. — Khanlou: The Coordinator

The coordinator pattern is a enterprise software design pattern that has been adapted to the iOS world:

You can remove duplication by placing all the flow logic in an Application Controller. Input controllers then ask the Application Controller for the appropriate commands for execution against a model and the correct view to use depending on the application context. — Martin Fowler

I like the tl;dr they posted at the top, it’s a pretty succinct definition of what coordinators are trying to accomplish:

A centralized point for handling screen navigation and the flow of an application.

For the purposes of this post I want place as much emphasis on the flow of the application as on navigation.

Observations

I spoke to a few developers at the try! Swift conference in NYC about their use of coordinators and everyone I spoke to had a different interpretation of their limits and responsibilities. Some of the developers I spoke to had defined functionality that is a requirement for anything being labeled a coordinator like a start method. Others used a coordinator protocol without any methods being required to bind the classes controlling their application architecture together.

From what I gleaned from my conversation with Soroush, the range of interpretations is sort of by design.

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. — Khanlou

Beyond the broad strokes of this definition, it’s really up to developers to decide how and when to use them.

Some Issues I’ve Come Across

Coordinator/Delegate Hybrids

All that being said, there were a few things that I’ve noticed when building out applications using coordinators. Because coordinators don’t have strict limitations on their responsibility, you can easily get carried away with them. One of the biggest issues I noticed with my projects is they can become coordinators view/view controller delegate hybrids. This isn’t necessarily a bad thing, however taken to an extreme we can end up in the same place we were trying to avoid in the first place by using coordinators: Massive-Coordinator-Controllers. You shouldn’t have to choose between using traditional iOS delegation and coordinators. In fact, finding a place for both has helped provide a natural structure to the pattern when there are very few guidelines.

Services

Services tend be a broad umbrella. In fact I probably use the category too broadly in many of my applications. If you think about it in relation to the real world, services take some kind of currency and and perform some action in return. In iOS a service can be as strict as a network client, or as broad as anything that works and transforms your models/UI. Figuring out how you want to layer your application and where each piece of functionality is relegated can provide a natural structural integrity to your architecture over the course of development. One thing I’ve taken away from the last few months is it can be helpful to decide how strict or broad you want to be with what classify as a service before you get started.

If I were rewriting the example project, I probably would not include a UI service.

Coordinators

I went through a few iterations for example code for this blog post. My first attempt looked something like this:

This isn’t bad, but at the end of the project I found myself writing this out, just to ensure I used the start method in my main coordinator:

func start() {

childCoordinators[0].start()

}

For me this was a clue that I wasn’t being broad enough with the overall coordinator protocol as well as not being specific enough with the behaviors of specialized coordinator subtypes.

What I finally settled on was this:

Coordinator Subtypes

Main Coordinator

One of the interesting things I came across when building PodCatch was the need to delineate different coordinators for types of logical flow. Although I eventually removed it, in my first version of PodCatch I had a sign in/sign up screen for a FireBase backend which was logically siloed off from the Podcast player. The responsibilities were so different that it seemed logical to separate the two areas. The question then arises, if these areas are separated do we need to control the flow between between them? In my case I went with an unequivocal yes.

One Abstraction To Rule Them All

Here is where I could see myself reading this article and saying: “Okay this guy is getting carried away.” You might be right! All I ask is that you hear me out on this. Remember this quote from a few paragraphs up?

Soroush: “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.”

Here’s the thing: I don’t see a reason this should only be true for view controllers. What’s stopping us from expanding on that idea one step further by layering our coordinations to make it that much more awesome! If we have clear sections of our applications whose internal coordination flow is separated, doesn’t it stand to reason that we can abstract coordination one step further?

Coordinators For Our Coordinators ¯\_(ツ)_/¯

Just bear with me for a bit longer. Let’s build the coordinator protocol for our top coordinator:

After that we can create class MainCoordinator which conforms to our AppCoordinator protocol like below:

After that we use protocol conformance to handle the flow between the different areas of our application:

The interesting thing about this design is it allows flexibility with the different areas of our application. Each section doesn’t necessarily need to have one overarching flow. Within a tabbar, we can have several different coordinator flows going for each tab.

As an added bonus, the ability to swap out, add or remove flows allows the architecture to work with various conditions say like having a setup profile flow until a user sets up their profile and then swapping that out with a view profile flow. That can be extrapolated out even further for different preferences etc.

Controller Coordinator

Side Note On Lessons Learned While Building PodCatch

One of things I would do differently if I was building PodCatch over from scratch is the way I went about creating the controller coordinator subtypes. In particular I regret creating a separate tab and navigation coordinator subtypes. It added additional complexity that, looking back, kept me from creating as organized architecture as I would have liked.

NavigationCoordinator:

TabCoordinator:

Unanswered Questions: Tab Bar Controller Coordinator?

The biggest issue is figuring out how to integrate the coordinator flows into something like a tab bar controller. Should a tab bar itself have its own coordinator? Is that overkill? Possibly, I’m still wrestling with the best way to approach this.

Back On Track

These are all interesting dilemas but following all the leads pulls this post in too many directions and ultimately muddies the waters. Let’s refocus on the example at hand. For this example project in my first go around I had something like this as a coordinator subtype.

That was probably enough. I wanted to demonstrate the the separation between application areas so I added a controller coordinator delegate (say that ten times fast) to help transition between the start view and color views. I ultimately settled on was this:

For me, even this is starting to get a bit overly complex for foundational layer of the application architecture. It still works and if you can find a simpler way to implement the same idea go for it. For now I’m still okay with it, I’m hoping to find a better way the more I experiment.

ColorControllerCoordinator

The first go around for implementing my ControllerCoordinator for this example was for the coordinator that handles my view controller subclass ColorViewController:

What immediately caught my eye with this implementation was the UI change happening in changeColor method. I think a better way to handle this is to inject styleService into the view and have the view handle up UI change/updates. So there’s definitely a refactor or two that can happen in here. Ultimately we’re working towards separating our concerns. For me, coordinators should not just be another place to throw code that isn’t the view controller.

Key Takeaways

There were a few things that stick out in my mind about coordinators that I picked up over the conference:

Children should never tell their parents what to do: One lesson that has stuck with me was something Brian at Raizlab mentioned: child controllers should never tell their parent controllers what to do. Meaning we shouldn’t be calling self.navigation controller from within a child view controller. Coordinators solve this for us, because in theory, with the logical abstraction of the application data flow out of our view controllers and into our coordinators we’re taking the decisions out of the parent/child view controllers hands. It’s one of those programming concepts that translates really well to human relationships (obviously with exceptions.)

Coordinators can be implemented in different ways:

Beyond pushing the logical flow out of the view controller, there’s no “one way” or right way to go about implementing. Mao’s probably not the best person to quote in an iOS blog post — but it’s sort of a “let a hundred flowers blossom” type of concept. It’s quite interesting to see the various permutations that grow from its simplicity.

Sources:

If you’re interested, there are some really great blog posts out there with people demoing their take on the concept: