Self-deallocated Coordinator pattern in Swift

How to solve child coordinators management

Albert Montserrat Gambus
VeepeeTech
Published in
6 min readOct 28, 2021

--

Introduction

It came in the moment when MVC pattern was extremely popular and and people started struggling with massive view controllers. We had view controllers with thousands lines of code, with many responsibilities (the view logic, the data processing logic, network requests, flow logic, etc.)

To abstract the navigation responsibility from view controllers, Khanlou introduced the concept of Coordinator pattern in one of his articles:

Paul Hudson has captured the essence of Coordinator pattern nicely in Hacking With Swift blog post:

Using the coordinator pattern in iOS apps lets us remove the job of app navigation from our view controllers, helping make them more manageable and more reusable, while also letting us adjust our app’s flow whenever we need.

After Khanlou article, many other articles started to put his ideas in practice:

This pattern is highly adopted in the iOS community to tackle the navigation flow of our apps.

Let’s dig into the Coordinator approach. The main idea is to have an object that handles the navigation for a single flow. We can consider that a flow represents a feature of your app that include a set of screens from A to B to perform this feature. To manage this flow, we need an object that is able to start the flow and navigate across the multiple screens. The protocol of a Coordinator can be as simple as this:

Let’s create an example of a Coordinator:

As you can see in the example, the coordinator just needs to know from where to start, and implement the start method.

Let’s see how we can handle inner navigation with the use of delegates:

Pretty simple from now, but let’s move forward!

Child coordinators

When you want to start a new flow you need to create a new coordinator and call start method in it. This new coordinator needs to be retained by someone because otherwise it would be deallocated and wouldn’t handle any delegates from his presented screens.

In this article, Paul Hudson describes the main problems with the Coordinator approach and how to solve them. One of them is how to handle navigating between different flows and back buttons.

All the articles previously mentioned, including the initial one from Khanlou, proposes to create a list of child coordinators allocated in the parent coordinator:

If you have ever implemented this approach, you will know that this starts to be complicated because:

  • You need to be sure to append the new coordinator to the list of children when it starts
  • You need to handle when the child coordinator flow ends, fetch the list of child coordinators, compare one by one with the one that request to be closed, and pop it from the list to allow him to be released
  • You need to handle any kind of back button from your screens because otherwise the coordinator wouldn’t be notified and wouldn’t be able to release the child coordinator
  • For this reason, you need to subclass navigation bar back buttons, implement the delegate of back swipe, pull to dismiss, etc.

This is how it may end up looking:

Self-deallocated child coordinators approach

In this article, I want to expose an alternative approach where Coordinators are not retained by its parent Coordinator. Instead, we use the default retain flow started from the view hierarchy. We will be using MVVM-C architecture to present this new approach, but it could equally be applied to MVC-C or any other architecture.

The only requirement is that we create a strong reference chain from the view until the Coordinator, no matter how many layers are in the middle.

In MVVM-C, self-deallocated Coordinator approach comes down to establishing the proper relationship between key components:

  • The presenter (usually a navigation controller) is holding the view
  • The view is holding the view controller
  • The view controller has to have a strong reference to the view model
  • The view model has to have a strong reference to the coordinator. Note that a coordinator can present one or more screens, so we can have multiple strong references of the coordinator.
  • To present the flows it’s needed that the coordinator have a reference of the navigation controller or any presenter. This reference must be weak!

Once the view is pop from the view hierarchy for any reason, the view controller is released. Then, following the chain, the view model is released too and the reference of the coordinator with it.

Note that if this is the last view model that was holding the coordinator, the coordinator gets automatically released too without any extra effort or callbacks that was needed using child coordinators.

Self-deallocated Coordinator pattern does not prevent the communication between view model and coordinator. This communication may be required to notify about some redirection or any other valuable information that the view model needs to send to the coordinator.

This can still be implemented using delegates or callbacks. We can also handle the moment where the view controller is dismissed implementing the dealloc method in either the view controller or the view model.

Implementation

In this link, there’s a POC of this approach. Let’s see how all the components are implemented:

Coordinator

We only need to have a reference to the navigation controller, just to enable navigating between screens.

The important thing here is that he reference needs to be weak because otherwise there would be a retain cycle between all the components. Also, the navigation controller is still retained by the view hierarchy, so there’s no need to extra retain it.

View models

We can use two different protocols; one to communicate back to the coordinator, and another to communicate to view controller.

The important thing here, is ensure that the delegate (the reference from Coordinator) must be strong, so we are retaining the Coordinator within the lifetime of ViewModel.

View controller

Again, the same important thing here is that view model is strong. Like this, we retain the ViewModel while the view is in the view hierarchy.

Benefits

One of the key benefits is the completely free management of coordinators, no need to extra retain or release child coordinators while it’s still possible to make specific actions on dismiss, pop, deinit, whatever you need, using delegate pattern, closures, or any other mechanism of your choice.

With the child coordinators array approach, you need to handle back buttons. However, iOS don’t only allow dismissing views with back button, but also left swipe or down swipe for presented view controllers. With this approach, no matter what system apple allow users to pop or dismiss view controllers, the behaviour will remain always the same. You neither need to create custom back buttons if you don’t really need it (as suggested in this article). This is all automatically handled by view hierarchy. ✌️

Disadvantages

One downside of this approach is that it might create some leaks if the retaining management of the views and view models haven’t been done correctly. Causing that even if the view is dismissed, the coordinator is not, because even if the view is not in the hierarchy anymore, this is not released, so it’s still retaining the view model and the coordinator.

On the other side, the same can happen using the child coordinator approach, and in this case, the memory leak is less visible and may be more difficult to find. Also, with child coordinator approach, you can forget to manually release child coordinators at some point, and this makes everything retained forever without you even know about that.

We encourage the use of Instruments and VM Debugging to ensure that all the components are retained and released correctly across the different app flows.

This new approach is experimental and in Veepee’s iOS app, we are starting to test it; So far, we are happy with the results!

--

--