Dependency injection is a design pattern to leverage inversion of control to manage dependencies. Rather than creating instances explicitly you ask someone for instances to fulfill a certain task. In iOS a typical dependency is a
NSNotificationCenter. Rather than getting the
defaultCenter() explicitly you should just ask for a
NSNotificationCenter to use, since it’s possible that the system doesn’t want to use the default center at the moment (e.g. in a unit test).
Unfortunately dependency injection is not that common in the iOS world but there are some great frameworks out there such as Objection.
For Swift the situation is a bit more difficult. Since it doesn’t have all the preprocessing macros - which is good because they were hard to debug and could mess up a lot - we cannot implement or use an annotation-based approach like Objection in Swift. But you don’t need fancy frameworks to do dependency injection and with the compact syntax of Swift it’s not that much effort to write it.
First of all lets collect the goals of the dependency injection solution we want to build:
- ask for dependencies - don’t tell
- encourage “program to interfaces, not implementations”
- provide a central definition of dependencies and configurations
- provide a central place for all singletons
- make the dependencies to other classes and especially singletons explicit
- make classes with dependencies easily testable
Lets start with the
AppContext which configures and creates all dependencies. This is the object which is asked to provide concrete instances.
The AppContext is a singleton and accessible via
AppContext.ctx. Singleton dependencies like NSNotificationCenter are defined as lazy vars and other dependencies are defined as closures returning new instances.
AwesomeViewController it’s easy to get the
NSNotificationCenter and a new
SampleAPIClient from the
The setup for an unit test with mocking of the SampleApiClient and providing an alternative NSNotificationCenter (using the defaultCenter in unit tests is a very bad idea because of side effects) could look like this:
We can go even one step further in terms of safety. At the moment anyone can change the configuration of the AppContext during the normal program runtime. This might be dangerous, cause someone could set another NSNotificationCenter and suddenly posted notifications do not reach all instances anymore! Lets improve this by using
let and protocols.
And for unit tests create a new
TestAppContext with the
var behaviour that was used before.
Have fun with cleaner and better testable Swift code! :-)