Building Applications With Coordinators: Part One
Taming Unruly Application Architecture
Note: Example project is not complete as of this writing, it’s more of a rough draft at the moment.
One area of development that I’ve been focusing on for the last few months is building modular code. I wrote back in January about the frustrations that I had when I was attempting to refactor TaskHero. At the time I said that it wasn’t my last word on the matter, that it was a problem that I was going to spend time reflecting over. In the interim, I’ve tried several approaches to application modularity with varying degrees of success. One of the more successful ways I’ve found to build modular application code has been with the Coordinator design pattern and delegation.
This post is the first of a two-part series on building applications using coordinators and the benefits/tradeoffs of using it. This first part mainly deals with the concepts and reasons why you would want to use it, the second part will go further into the implementation. I’ve built an example project, and it’s still not in the final form, it’s more of the rough draft as of this writing. If you are interested in checking out the example, you are more than welcome to. I posted a link to it at the top.
Credit Is Due
If you haven’t read Soroush Khanlou’s blog, I highly suggest you start to. It’s a great resource and has helped change the way I approach application architecture. This post was inspired by several of his articles. In particular I recommend reading his series of articles on using coordinators.
MVC Can Mean More Than One Thing
One of the most commonly used applications architectures out there Model-View-Controller and it’s derivatives.
The Model-View-Controller (MVC) design pattern assigns objects in an application one of three roles: model, view, or controller. The pattern defines not only the roles objects play in the application, it defines the way objects communicate with each other. Each of the three types of objects is separated from the others by abstract boundaries and communicates with objects of the other types across those boundaries. The collection of objects of a certain MVC type in an application is sometimes referred to as a layer — for example, model layer.
If you live in the Apple programming universe, you quickly learn that Apple uses the MVC architecture all over. In fact MVC is baked into UIKit at its core. While MVC is an extremely useful design pattern, if it is not handled properly there are some drawbacks. These pitfalls aren’t necessary apparent at first blush, especially if you are new to programming.
One of the fundamental problems with ViewControllers is that they can quickly become massive and unwieldy as your application development progresses. This can cause multiple problems. To begin with, from a practical perspective, it can be hard to work with a massive class that has many different parts of the applications functionality thrown together within it.
Because the purpose of ViewControllers can get interpreted broadly, it can be tempting to put code that doesn’t seem to fit anywhere else inside them. This can lead to MVC Syndrome — Massive-View-Controller. The problem with ViewControllers can be like Singletons in that their purpose is open to interpretation, and therefore anything could plausibly belong to them.
ViewModels To The Rescue?
One of the first places most people look when trying to get over MVC syndrome are ViewModels. And why not? It’s a way to add another layer of abstraction to your application, which is what we want, right? Well sort of, it’s definitely a step in the right direction.
ViewModel are abstractions of a View’s display logic. Abstractions are good because because they decouple different areas of your application. Abstracting different aspects of your applications functionality is how we go about untangling our code.
Part Of The Answer — Not The Solution
ViewModels are not the silver bullet solution to our dilemma though, problems can arise with them as well. To begin with, it becomes all too easy to hide all the code you would have stuffed in a ViewController in your ViewModel creating an entirely new syndrome: Massive-View-Models. When this happen our ViewController can look beautiful and clean, but the dirty secrets are hidden in the ViewModel. What we get with ViewModels is a piece of the puzzle, and their utility should not get discounted, but not the whole thing. Hiding chaos is not what we are striving towards here, what we want is modularity.
Note: If you’re interest in going deeper into ViewModels I suggest checking out this article.
When our code is brittle, minor changes can cause problems throughout your application. Our code becomes brittle when we bind different areas of our application’s functionality together. The more intertwined and entangled our code becomes, the greater the more brittle our code becomes. Logically this makes perfect sense. Overlapping interdependence means that changes one area of the application cascade through the entire system.
One of the biggest problems with the big view controllers is that they entangle your flow logic, view logic, and business logic. — The Coordinator
In software development, modularity is the opposite of brittleness. Modular code means that different areas of functionality are largely self contained or only loosely dependent on other classes.
Promoting Encapsulation and Modularity
Writing modular code has several benefits. For one, it’s becomes easier to refactor as you go along because functionality is loosely coupled. You’ll find that you can make major changes to one aspect without having to change code all over. Modularity also makes writing tests for our code much more simple and effective by allowing us to test different aspects of code in isolation or composition easily.
In OOP inheritance can be a gift and a curse, depending on its implementation. When used correctly, inheritance enhances application modularity and promotes DRY code. When used incorrectly, you can end up an application that is chalk full of clown-car classes. Clown car classes are classes that get stuffed full of unnecessary properties and methods that get added on at each level of the inheritance chain. This is where delegation can come in. We can use delegation to construct classes as needed instead of carrying around older baggage.
“Delegation is like inheritance done manually through object composition.” — Best Practice Software Engineering
By using delegation, we can use protocol conformance to make our code more reusable as well as more modular.
Coordinators can help you restore order and tame unruly application architecture. When we use coordinators, we begin stepping away from MVC, albeit not entirely. Coordinators take the place of ViewControllers as the highest arbiter of the flow of our application’s logic. In fact, a coordinator is the abstraction of that logical flow. We accomplish this abstraction with liberal use of delegation.
I find that when I’m using the coordinator pattern, I’m naturally more inclined to make my code more modular. To be honest I’m not sure whether this is due to the mindset that the pattern engenders or if modularity is the path of least resistance with coordinators. Regardless, I found that several problems I had been working on had been solved without trying to solve them.
Abstractions Of Abstractions Of Abstractions
One of the first issues I ran into was the need for multiple coordinator types. Different controllers require different types of coordinators. You can’t control the flow of a UINavigationController the same as you would a UIViewController and the same is true when you consider the differences between UINavigationControllers and UITabBarControllers. This calls for one final abstraction, one abstraction to rule them all!
One Abstraction To Rule Them All
Different Types Of Coordinators
To deal with the differences in our controllers we create different kinds coordinators that conform the base Coordinator protocol.
I think this is a good place to break off. I know a lot of this was theory, but don’t worry! Next post we’ll dive more deeply into the code…