FREE: Read a new Swift article every day – click here! >>

Using closures with the coordinator pattern

Paul Hudson       @twostraws

I talk about coordinators a lot, but there are two questions I get asked a lot:

  • How is it different from the delegate pattern?
  • Why not use closures?

I figured it was time I wrote down permanent answers to these questions so I had something to point folks to.

Coordinators: the two-minute recap

Before we dive in, I want to briefly outline what coordinators are for those of you who haven’t heard of them before. Coordinators originated with Soroush Khanlou way back in his 2015 article, then were expanded upon in several follow up articles. I’ve also written about the pattern, but I highly recommend you read Soroush’s work.

At their core, coordinators allow us to take navigation logic out of our view controllers, so they have no idea what comes next in our app flow or even that an app flow exists. This means your view controller no longer needs to know about, create, configure, then display an entirely different view controller.

So, instead of writing this sort of code:

navigationController?.pushViewController(detailVC, animated: true)

We instead add a coordinator property to our view controller, ideally using a protocol so we have more flexibility, then call methods on it when something happens:

coordinator?.purchase(product)

Using this approach, the coordinator is responsible for deciding what should happen next: it might take one path for iPhone and another for iPad, it might do different things based on which A/B test the user is in, and so on.

Why not just use delegates?

The delegate pattern is hugely important in the world of Apple development. In fact, in my book Swift Design Patterns delegation is literally the first pattern that’s covered – it’s extremely useful for breaking out functionality, and is a central technique for tackling massive view controllers.

But here’s the trick: coordinators are just specialized delegates. Take a look at this code again:

coordinator?.purchase(product)

If you want to call it a delegate instead, our code magically transforms into this:

delegate?.purchase(product)

All that’s changed is the name of the property being used – the technique of isolating then carving off navigation code hasn’t changed. That being said, using something specific like coordinator allows you to be more fine-grained in your delegation – you might have several different delegates all working together to support a single object, because there’s no point trading massive view controllers for massive delegates.

Using closures for iOS navigation

When a view controller has only a handful of coordinator callbacks, I often use closure injection rather than sending in a coordinator instance.

So, rather than having a property like this:

weak var coordinator: MainCoordinator?

I would instead have properties like this:

var readAction: ((Article) -> Void)?
var buyAction: ((Product) -> Void)?

That allows to inject callbacks into a view controller rather than a specific coordinator. This has the effect of creating even more loose coupling than having a coordinator object or protocol, allowing us to replace coordinators with something else entirely if we wanted.

From the view controller end, we can trigger those closures like this:

func read() {
    readAction?(someArticle)
}

From the coordinator end – because coordinators still exist, and still do all the job of controlling navigation – we would write something like this:

detailVC.readAction = { [weak self] in
    self?.read(article: $0)
}

Note: Make sure you create a new closure and use weak self to avoid the possibility of a retain cycle.

This technique of closure injection is nothing new – in fact, Apple even mentioned it in their WWDC 2016 presentation, Improving Existing Apps with Modern Best Practices. However, I think it has its limits: if you try to pass in more than a couple of closures it gets messy, and you should consider using a coordinator object or protocol instead.

Bonus alternative: protocol composition

There’s one other technique I’d like to mention briefly, because it’s one I find extremely useful.

When making a small app with coordinators, my view controllers have a concrete coordinator type as their property rather than a protocol, like this:

weak var coordinator: MainCoordinator?

When making more advanced apps I’ll add in coordinator protocols that allow me to change which concrete coordinator implementation is being used without affecting anything else, like this:

weak var coordinator: Purchasing?

Coordinator protocols work great for medium-sized apps, but for larger apps I switch over to using protocol composition, like this:

var coordinator: (AnswerHandling & AlertHandling)?

That way I get the benefits of being able to switch concrete implementations, but I also get to be more flexible in breaking up functionality.

Which works best?

Regardless of whether you choose concrete coordinators, protocols, protocol composition, or closure injection, the point is that you’re burdening your view controllers with one less responsibility. All four of those techniques are useful at different points of app development, so it’s worth trying them all out to see which one works best for your team.

Alternatively, if you have a different way of working with coordinators I’d love to hear more – get in touch on Twitter at @twostraws!

 

MASTER SWIFT NOW
Buy Testing Swift Buy Practical iOS 12 Buy Pro Swift Buy Swift Design Patterns Buy Swift Coding Challenges Buy Server-Side Swift (Vapor Edition) Buy Server-Side Swift (Kitura Edition) Buy Hacking with macOS Buy Advanced iOS Volume One Buy Advanced iOS Volume Two Buy Hacking with watchOS Buy Hacking with tvOS Buy Hacking with Swift Buy Dive Into SpriteKit Buy Swift in Sixty Seconds Buy Objective-C for Swift Developers Buy Beyond Code

About the author

Paul Hudson is the creator of Hacking with Swift, the most comprehensive series of Swift books in the world. He's also the editor of Swift Developer News, the maintainer of the Swift Knowledge Base, and Mario Kart world champion. OK, so that last part isn't true. If you're curious you can learn more here.

Was this page useful? Let me know!

Average rating: 5.0/5

Click here to visit the Hacking with Swift store >>