iosdevswift

Passing dependencies through Coordinator chain

As described in the original post about Coordinators and MVC-C architecture, the top logical entity in your app should be AppCoordinator.

This Coordinator will then create and keep references to instances of other coordinators; either directly keeping references to them or through childCoordinators.

Apart from AppCoordinator, most other Coordinators are most likely content-oriented. Which means they will have a set of UIViewControllers that they display for specific purpose. How they do that – create UIVCs, present them etc – it’s their own business so it makes no sense to expose that as public. However, there should be a way for other coordinators to specify the desired content. Example: remote notification handler may instruct you to display a specific promo offer inside CartCoordinator.

This concept of the content page/view, I modeled through simple enumeration inside each content Coordinator, like this:

enum Page {
	case cart
	case checkout
	case payment
	case offer(String)
}
var page: Page = .cart
func display(page: Page) {
...
}

enum is great for this since it’s dead simple to scan and it’s straightforward to supply whatever set of parameters the page may need.

Now AppCoordinator is able to create / activate CartCoordinator and specify what it should display, without knowing how that will be done.

Here’s a typical method:

func showCart(_ page: CartCoordinator.Page?) {
	let identifier = String(describing: CartCoordinator.self)
	//	if Coordinator is already created...
	if let c = childCoordinators[identifier] as? CartCoordinator {
		c.dependencies = dependencies
		//	just display this page
		if let page = page {
			c.display(page: page)
		}
		return
	}

	//	otherwise, create the coordinator and start it
	let c = CartCoordinator(rootViewController: navigationController)
	c.dependencies = dependencies
	if let page = page {
		c.page = page
	}
	startChild(coordinator: c)
}

...

showCart(.offer(offerId))

CartCoordinator will fetch the offer using provided offerId from the remote notification and push the appropriate UIViewController.

To do that, it will probably need access to some DataManager and/or APIManager. Instances of those objects are kept at AppCoordinator and are passed-down using that one line of code I sneaked in the previous code snippet:

c.dependencies = dependencies

What is dependencies?

AppDependency

It’s the simplest possible struct with bunch of optional properties:

struct AppDependency {
	var apiManager: Empires?
	var dataManager: DataManager?
	var promoManager: PromoManager?
	var settings: Settings?

	var persistanceProvider: RTCoreDataStack?
	var moc: NSManagedObjectContext?

	init(apiManager: Empires? = nil,
	     persistanceProvider: RTCoreDataStack? = nil,
	     dataManager: DataManager? = nil,
	     moc: NSManagedObjectContext? = nil,
	     promoManager: PromoManager? = nil,
	     settings: Settings? = nil)
	{
		self.settings = settings
		self.promoManager = promoManager
		self.apiManager = apiManager
		self.persistanceProvider = persistanceProvider
		self.dataManager = dataManager
		self.moc = moc
	}
}

Using the provided init, you can create instance of this struct with any combination of app-wide dependencies.

I enforce the requirement through simple protocol, adopted by each Coordinator I create:

protocol NeedsDependency: class {
	var dependencies: AppDependency? { get set }
}

What I need to make sure is that whenever dependencies is set, that new value is passed-down to all its child Coordinators.

final class CartCoordinator: Coordinator<UINavigationController>, NeedsDependency {
	var dependencies: AppDependency? {
		didSet {
			updateChildCoordinatorDependencies()
		}
	}
}

Implementation of this particular method is easy, using protocol extensions:

extension NeedsDependency where Self: Coordinating {
	func updateChildCoordinatorDependencies() {
		self.childCoordinators.forEach { (_, coordinator) in
			if let c = coordinator as? NeedsDependency {
				c.dependencies = dependencies
			}
		}
	}
}

And that’s about it. In the next post in the Coordinator series I’ll explain how to use AppDependency with UIViewController and especially how to handle the case where some of the dependencies may not yet be available when it’s requested.

As always, keep an eye on Coordinator repo as I keep adding small fixes and refinements.