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.