I’ve been using protocols for dependency injection in Swift for a while, but recently I came up with a nifty little function that helps with the task using generics. Using protocols for dependency injection is a great way to abstract your code and make it more flexible. Say you get an unnamed view controller as a segue.destinationViewController property. In Swift you don’t need to cast it to specific class instance. Instead you can check for protocol conformance. “Is this X” vs “is this something that wants Y” – identity versus function. If later you add a segue to another conforming controller you won’t have to add a line of code.

So here’s that generic function that helps with this pattern:

1
2
3
4
5
6
7
8
9
10
11
func injectDependencies<Protocol>(
  into viewController: UIViewController,
  @noescape block: (Protocol) -> Void
) {
  if let conformingController = viewController as? Protocol {
    block(conformingController)
  }
  for controller in viewController.childViewControllers {
    injectDependencies(into: controller, block: block)
  }
}

This will traverse any table or collection view controllers that you might be using.

So in my app any controller that talks to the network does so through CacheManager which handles the requests and returns results using NSFetchedResultsController from the cache. I create the cache manager once in AppDelegate and propagate it to any controller that might need it.

1
2
3
4
5
6
7
8
9
10
11
12
13
func application(
  application: UIApplication,
  didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?
) -> Bool {
  cacheManager = CacheManager()

  injectDependencies(into: window!.rootViewController!) {
    (controller: ControllerWithNetworkAccess) in
    controller.cacheManager = cacheManager
  }

  return true
}

And then in segues of controllers that reference cacheManager:

1
2
3
4
5
6
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
  injectDependencies(into: segue.destinationViewController) {
    (controller: ControllerWithNetworkAccess) in
    controller.cacheManager = cacheManager
  }
}