Aleksandar • Vacić

iOS bits and pieces

MVC-C: Injecting Coordinator pattern in UIKit

I first learned about Coordinators from Soroush Khanlou’s talk at NSSpain 2015 and two related articles on his blog.

A bit later, Krzysztof Zabłocki wrote about more or less identical concept he calls Flow Controllers.

I got intrigued because it was the first iOS app architecture approach I’ve seen that (from my point of view) didn’t try to replace nor denigrate MVC — it embraced and extended it. It builds on existing design patterns: Model View Controller (MVC), Delegation and Dependency Injection (DI), all already proven to work well in practice.

In this article I want to present my evolution of the Coordinator approach that helped me conquer rather large app with 10 or so distinct parts and 100+ UIViewControllers in total.

But first — quick intro to where Coordinators fit in the iOS app architecture.


Let’s imagine a shopping cart app. I actually built a simple one as example project accompanying the library on Github.

Essential goal for this app is: customer adds items to shopping cart from anywhere in the app and completes the purchase of those items at any given moment.

It’s quite likely that you may have multiple features showing items for sale.

  • Promoted/clearance items in one or more carousels
  • Browse feature through one or more collection views
  • Controllers nested inside different controllers etc.

Lots of UI options your designer can choose to use. Key thing: regardless of where the item is shown, it should have a buy now or add to cart button. This is e-commerce app thus its key purpose should be available with as little friction as possible.

So let’s break down the high-level parts: you at least need My Account, Cart, Browse-Catalog. Each of these parts can have just one or two views up to maybe dozens. Then you also have minor parts which can overlap and/or be shared between those high-level parts. Things like: register payments or maybe deposit into an account or enter/retrieve a shipping address. Each of these can end up being a world of their own where you need to potentially support dozens of different payment options - cards, web wallets etc. Which often have strict requirements how they need to be presented. So dozens of additional controllers and views.

Payments can be initiated in the My Account but are needed inside the Cart as well. Or directly on the browsing views if you have 1-click buy. So that means that you may need to switch quickly to an entirely different context. And be able to go back to original context, whatever it is. So your poor PaymentViewController needs to know the context/conditions in which it was initiated and where it should go back once it’s done.

Adding items to cart is also prone to similar complexities. Regardless of where I tap, no matter how deep in the catalog display hierarchy I am — that chosen item should find its way to the Cart. For that to work, I either need to pass some CartManager instance all the way down to the leaf nodes or…maybe target a method on CartManager singleton. (I can almost sense you shudder) No matter which method you choose, your UICollectionViewCell or nested child UIViewController or whatever…is now deeply bound to the Cart Whatever.

You see where I’m going with this..?

This is what causes M(assive)VC problem people complain so much in iOS world. Controllers take up more and more responsibility as you add features and interconnect the app parts.

I’ve seen way too many people blame Apple1 and Cocoa flavor of MVC for this. I find that really unjustified; the onus here is on the developer at hand, not Apple nor Cocoa.

Let MVC be MVC

What we need here is architecture that lets MVC do the task it was designed to do - handle display of one visual component and do nothing else.

MVC usage rules in UIKit:

  • UIViewController should not know nor care about the hierarchy it’s presented in (is it inside the nav, split, popover, whatever)
  • It should be able to send info about events that happened in it — button taps, row/cell taps, swipes, any kind of interaction — to its semantic parent
  • It should not make any network requests by itself, it shouldn’t even know nor care is there network connection available but
  • It needs to be able to request data and receive either the data or error/info about why data is not delivered

In other words, it should be configurable from outside (Dependency Injection at work). It should receive only the subset of app’s data set it needs to.

For the output: if you find your self calling self.navigationController or self.parentyou have failed this pattern2 called MVC. It’s not the job of view controller to create nor decide the fate of other UIVCs.

Coordination between controllers and data flow through the app is someone else’s job — enter the Coordinators.

Coordinator job

  • Create instances of the VCs
  • Show or hide VCs
  • Configure VCs
  • Receive data requests from VC
  • Route requests to network, data, account etc. managers
  • Route results back to VC

Since their job is to handle data flow then it’s their job to know about the parent-child relationships in the app thus they also deal with coordinator hierarchy.

Hence each Coordinator can be parent and/or child of some other Coordinator.

How to use Coordinators

In our shopping cart app, we would first need a high-level object that represents root Coordinator, more often called Application Coordinator. This object will create instances of all other specific *Manager stuff, like NetworkManager, API wrapper, DataManager, CartManager etc. It also contains logic which decides what will be set as window.rootController. Said plainly — it does what people usually do in AppDelegate.

High-level app parts are covered by AccountCoordinator, CartCoordinator, CatalogCoordinator. Then you have focused stuff like PaymentCoordinator which deals just with payments. You may also have NotificationCoordinator that handles in-app presentation of the received notifications. Etc.

The only object that’s always in the memory is ApplicationCoordinator and is kept with strong property on the AppDelegate. All the others are allocated and removed as needed. Each of those others can be a child of any other Coordinator, there is no fixed structure. So you can have any of these hierarchies currently active:

1
2
3
4
App → Catalog → Payment
App → Account → Payment
App → Catalog → Cart → Payment
App → Payment

Each Coordinator uses Dependency Injection with protocol composition to receive what it needs to operate properly.

Since Coordinators will load and initiate display of more than one View Controllers, they need some UIKit entity for that. Hence each Coordinator has rootViewController property which can be set to instance of any subclass of UIViewController. Active Coordinator will always set itself as parentCoordinator for its root view controller.

Usually that’s UINavigationController instance and Coordinator will simply do rootViewController.show(vc, sender: self) when it needs to show display controller.

Communication

Now, this is the key part of my approach.

In his talk, Soroush recommends to use DI pattern for input, which I agree with, as shown above.

For output, he went with Delegate pattern; not just for Coordinators but also for VCs where VC.delegate will be set to their containing (parent) Coordinator.

At first, I went with that approach. However, I found it lead to whole lot of boilerplate and repeated code.

I don’t know is it my luck to attract clients with affinity for complicated UIs but in last 4 projects I had to implement my own container controllers, in some cases with 2 or 3 levels deep nesting. Designs where you need to present vastly different UI from separate data sources, all on single screen.

So in our imaginary shopping app, I mocked this up with the slider at the top being a UICollectionView nested inside the cell of another collection view. In Reveal it looks like this:

Now, see that green button which triggers Buy Now for the given item? It’s inside PromotionCell, which is inside PromoContainerCell, which is inside HomeController’s collection view, which is nested in UINavigationController which is owned by CatalogCoordinator which is a child of ApplicationCoordinator where the CartManager instance is likely kept.

So if I implement Delegate pattern to pass this Buy Now action up, each level will need to adopt the protocol containing the cartBuyNow(:) method in order for it to reach the top. Even controllers that have no need for it, like PromoContainerCell or HomeController. Their job is to read and display the data, not care about taps on some small part of the UI. Even if that UI is in their subview hierarchy.

One way to solve this is to pass CartManager as dependency all the way from the top down to the bottom. But then — why would all the objects higher in the hierarchy care about CartManager? The precious few that need to care are maybe CartCoordinator and CartViewController which are not in that hierarchy. All the others - not so much.

(You see now why Singleton trap is so easy to fall into? I just add a tiny call to CartManager.shared.cartBuyNow() and problem solved, right?)

So, key problem is: how to transfer this cartBuyNow call up the stack without adding too much code?

Inspiration

I first looked for possible solution in UIKit. Because there’s already very similar functionality in it:

1
2
3
class UIViewController {
  func show(_ viewController: UIViewController, sender: Any?)
}

This is a replacement for the self.navigationController.pushViewController() we all used before iOS 8. (Please, don’t ever use it today)

This is good candidate: one method without any required boilerplate in my code; that you call on any UIViewController and with default implementation that simply calls parent.show(…). UINC (and few others) override this method and implement their own specific behavior — for UINC that means doing push. Perfect message bubble-up through the given hierarchy.

Sadly, this is not usable for me since:

  • It only works with UIViewControllers
  • I need it to work with Coordinator hierarchy too
  • I also need it to work with UIView, since action sender can be a simple UIButton in the UI*Cell or something similar

Looking more closely at this I realized that this behavior is based on class inheritance and overriding a particular method (show) and/or setting a property (parent). And this gives us the solution…

What’s the common ancestor for both UIView and UIViewController?

UIResponder

Thus all I need to do is:

  • class Coordinator: UIResponder {…}
  • find a replacement for parent that spans both UIView and UIViewController and
  • can be implemented on Coordinator as well

First one is straight-forward.

Second one was easy once I read the docs on how UIResponder actually works, meaning how it knows what’s the next responder in the chain:

1
2
3
class UIResponder : NSObject {
  open var next: UIResponder? { get }
}

Well, hello there — I can piggy-back on UIResponder.next without actually interfering with it.

1
2
3
4
5
public extension UIResponder {
  public var coordinatingResponder: UIResponder? {
      return next
  }
}

With this, I can implement my own custom messaging system that will bubble up through the entire hierarchy in one-line method:

1
2
3
4
5
extension UIResponder {
  func messageTemplate(args: Whatever, sender: Any?) {
      coordinatingResponder?.messageTemplate(args: args, sender: sender)
  }
}

Anywhere I need a custom behavior or specific handler for this method, I simply override messageTemplate and…done.

Inter-connect with Coordinator

Remaining issue to solve: if Coordinators are controlling the UIVCs, then in order for the bubble-up to work, each UIVC instance needs to know what is its (optional) parent Coordinator.

Simple with little help from ObjC runtime:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
extension UIViewController {
  private struct AssociatedKeys {
      static var ParentCoordinator = "ParentCoordinator"
  }

  public var parentCoordinator: Any? {
      get {
          return objc_getAssociatedObject(self, &AssociatedKeys.ParentCoordinator)
      }
      set {
          objc_setAssociatedObject(self, &AssociatedKeys.ParentCoordinator, newValue, .OBJC_ASSOCIATION_RETAIN)
      }
  }
}

I need to thread carefully here…

Excerpt from UIResponder.next documentation:

The UIResponder class does not store or set the next responder automatically, instead returning nil by default. Subclasses must override this method to set the next responder.

UIView implements this method by returning the UIViewController object that manages it (if it has one) or its superview (if it doesn’t);

UIViewController implements the method by returning its view’s superview;

UIWindow returns the application object, and UIApplication returns nil.

So I need to override UIViewController.next and jump to the Coordinator if the UIVC.parentCoordinator is not nil. Otherwise, I need to replicate exactly what documentation describes:

1
2
3
4
5
6
7
8
extension UIViewController {
  override open var coordinatingResponder: UIResponder? {
      guard let parentCoordinator = self.parentCoordinator as? UIResponder else {
          return view.superview
      }
      return parentCoordinator
  }
}

Lastly, on the Coordinator level:

1
2
3
4
5
6
7
open class Coordinator<T>: UIResponder {
  open var parent: Any?    
  
  override open var coordinatingResponder: UIResponder? {
      return parent as? UIResponder
  }
}

Benefits

The main benefit is that this entirely removes the need for repeated mediating-only Delegate patterns. This is huge saving in number of code lines in large, complex apps. Plus it simplifies things immensely. Take a look at the updateData method in HomeController.swift in the example app. It’s heavily commented and it’s excellent example how you can implement seamless data updates.

Second: unit testing is trivial for message like this: showMarkets(node: Node, sender: Any?). The UIVC using such messages is also easy to test, as you need to supply input that’s completely in the domain of the given VC, no other dependency at all. All the data each VC needs are those marked as local data model, which are injected into it.

Even if the data fetch message has completion closure (escaping or not) that returns requested data back, you know exactly what the completion arguments are and can write tests for each variant.

One advice though: don’t go crazy with this. Use it only for messages that span multiple levels of hierarchy. If you have close parent-child containment for some UIViewController then please do use Delegate pattern for that.

I have not seen any performance issues with coordinatingResponder but still — let’s not overcrowd the UIResponder space more than it’s necessary.

Third benefit is related to popups/popovers since they are not part of the flow you (or at least I) would except. UIResponder.next for the presented UIVC is not going through presentingController as I expected but instead goes directly to window. Which means that if you present a UIVC you break the chain. But if you wrap your popups in their own Coordinator (which I consider the correct approach), you have full message bubble-up all the way to ApplicationCoordinator and do not interfere with UIKit behavior at all.

Downsides

The largest: you can’t use Swift-only types for message arguments since they must be representable in Objective-C. Thus you can’t use things like Swift enums or structs for arguments.

Solution for this is to wrap your structs and enums into a simple class and then box / unbox as needed.

1
2
3
4
5
6
7
8
9
10
11
12
struct Color {}

class ColorBox: NSObject {
  let unbox: Color
  init(_ value: Color) {
      self.unbox = value
  }
}

extension Color {
  var boxed: ColorBox { return ColorBox(self) }
}

Moreover, even class-based model object must inherit from NSObject or implement NSObjectProtocol on their own. Otherwise you will get strangely sounding error saying:

Declarations from extensions cannot be overridden yet

The message itself sounds like a temporary issue with Swift so I hope it will be gone at some point in the future. For me personally, this is not an issue at all as in larger apps I use Core Data for persistence thus all model objects are subclasses of NSManagedObject. But if you have gone entirely with structs, this may be a deal breaker. Even then though, I believe that extra box/unbox steps needed are worth it for other benefits.

Second issue: Your message overrides must be in the original class definition due to Swift limitations at the moment. So you can’t place them in the class extension, where I would very much like them to be.

Third: in most cases you need to wait until viewDidAppear in order to use this system. This is because that’s the only moment I found that UIView.next is always reliably set for all the views in the nested hierarchy. I first thought didMove(toParentViewController:) could also work but found issues with it too so fell back to viewDidAppear.

In conclusion

The beautiful result of this architecture was that moving display VC from one coordinator to another required absolutely no changes in the VC itself. Only at the Coordinators to instantiate the controller and to process the output. Which again required relatively small amount of changes.

This approach of environment-agnostic VCs gave me large degree of freedom in the mentioned app with 100+ controllers since a good deal of them was moved around during development.

The high-level UI architecture of the app changed from UITabBarController to UISplitViewController and ended with entirely custom-made TabBar-look-alike powered by subclassed UINavigationController which came up one (full-time) month in. During all that, I did not need to change a single line in any of the display VCs.

Further — many of those display Controllers shared one or few actions that had to end up at the same high level object, similar to the Buy Now example.

All in all, I hope you will give my Coordinator approach a chance. This entire mini-library is one single file so it’s almost a crime to use it as Cocoapod and waste LLVM’s time ;) although you can if you want to.

Feedback through PRs and on Twitter — I’m @radiantav — is very much appreciated.

I would especially love it if any of you Swift wizards would look into this:

1
2
3
4
5
open var parent: Any?

///  A dictionary of child Coordinators, 
/// where key is Coordinator's identifier property
open var childCoordinators: [String: Any] = [:]

as I would you really like it to be:

1
2
3
open var parent: Coordinator<XX>?

open var childCoordinators: [String: Coordinator] = [:]

I received some very useful feedback at UIKonf 2017 and will continue to improve this pattern and extend the example app to show more elaborate uses, so keep it bookmarked on GitHub.


  1. Being totally honest, they do deserve some flakk for the self.navigationController and similar stuff. Luckily, that’s in the past now.

  2. nod to Green Arrow