The Typhoon framework (http://appsquickly.github.io/Typhoon/) is probably the most famous DI Container in the Cocoa ecosystem:
It's full of features, but it isn't too big, just around 3,000 lines of code. It is well-documented because the maintainers are committed to improving it.
The Typhoon building blocks are called assemblies, and they look like factories. Let's see the code provided as a sample app on their website (https://github.com/appsquickly/Typhoon-Swift-Example):
public class ApplicationAssembly: TyphoonAssembly {
//...
public dynamic func citiesListController() -> AnyObject {
//...
}
public dynamic func weatherReportController() -> AnyObject {
//...
}
public dynamic func weatherReportView() -> AnyObject {
//...
}
public dynamic func addCityViewController() -> AnyObject {
//...
}
}
In the implementation, instead of returning a concrete instance of the requested type, we return a description of how the instance should be created, which is a TyphoonDefinition type:
public dynamic func citiesListController() -> AnyObject {
return TyphoonDefinition.withClass(CitiesListViewController.self) {
definition in
definition!.useInitializer("initWithCityDao:theme:") {
initializer in
initializer!.injectParameter(with: self.coreComponents.cityDao())
initializer!.injectParameter(with: self.themeAssembly.currentTheme())
}
definition!.injectProperty("assembly")
} as AnyObject
}
The definition contains the dependencies needed to be injected, and which function must be called to do the Injection, which in this case is a constructor. In the definition, we can set the scope, which is a singleton here:
public dynamic func rootViewController() -> AnyObject {
return TyphoonDefinition.withClass(RootViewController.self) {
definition in
//...
definition!.scope = TyphoonScope.singleton
} as AnyObject
}
The singleton type means that the object is created during the setup of the dependencies and then retained by Typhoon until the end of the app's life cycle. The default scope is TyphoonScopeObjectGraph, which means that the instances are retained for the time of the assembly to create the object graph and then passed the ownership to the object graph. This permits us to create a list of the objects retained to a high-level scenario, let's say a view controller.
The assemblies must then be defined in Info.plist:
Using Objective-C runtime inspection, Typhoon then instantiates the assembly and injects it in all the objects.
The object that needs an instance from the assembly then calls the building function in its assembly instance:
self.citiesListController = UINavigationController(rootViewController: self.assembly.citiesListController() as! UIViewController)
For the testing, the assembly must be created and activated explicitly:
public class CityDaoTests : XCTestCase {
var cityDao : CityDao!
public override func setUp() {
let assembly = ApplicationAssembly().activate()
self.cityDao = assembly.coreComponents.cityDao() as! CityDao
}
//...
}
As we have seen, Typhoon is quite easy to use, but it relies on Objective-C runtime, and the API isn't really nice to use in Swift. Methods called during the Injection must be dynamic, and the class must be subclasses of NSObject. There is a Swift version (https://github.com/appsquickly/TyphoonSwift) with a more Swift-ly API, but it doesn't look as actively maintained.