I recently read the article “Using protocol compositon for dependency injection” by Krzysztof Zabłocki. I found this to be a rather interesting technique for doing DI, but when trying to implement it I found it somewhat limited:
- It doesn’t really handle dependencies that have dependencies themselves.
- No support for lazy instantiation, everything needs to be instantiated up front.
Lets look at simple and pretty common example of the object graph I wanted to create:
- A view controller at the leaf level, which needs
- An object that provides business logic (like the Interactor from the VIPER pattern), which needs
- the data store (a NSPersistentContainer in my case).
This can be translated pretty easily to this pattern:
protocol HasCoreData {
var coreData: NSPersistentContainer { get }
}
protocol HasListInteractor {
var listInteractor: ListInteractor { get }
}
struct AppDependencies: HasCoreData, HasListInteractor {
let coreData: NSPersistentContainer
let listInteractor: ListInteractor
}
final class ListViewController: UIViewController {
typealias Dependencies = HasListInteractor
let dependencies: Dependencies
init(dependencies: Dependencies) {
self.dependencies = dependencies)
// ...
}
}
final class ListInteractor {
typealias Dependencies = HasCoreData
let dependencies: Dependencies
init(dependencies: Dependencies) {
self.dependencies = dependencies)
// ...
}
}
This all works fine until I try to fill in my AppDependencies struct – to create it I need an instance of my ListInteractor, and to create that I need the AppDependencies struct. So this is not going to work.
Instead I could create a second struct that only implements HasCoreData and use that to initialise the ListInteractor. This approach will work, but will quickly become messy and as hard to refactor as passing every dependency separately. It also won’t address the point of avoiding eager initialisation.
But there is a simple solution that addresses both problems:
final class AppDependencies: HasCoreData, HasListInteractor {
lazy var coreData: NSPersistentContainer = NSPersistentContainer(name: "Whatever")
lazy var listInteractor: ListInteractor = ListInteractor(dependencies: self)
}
By making the AppDependencies a class instead of a struct we can use lazy properties to fulfil the protocol requirements. That way we can’t get into the catch-22 situation I described and we get lazy instantiation for free.
Of course there are some other drawbacks to consider:
- Cyclic dependencies will cause infinite recursion
- Objects are created lazily, so if you need eager instantiation of some class you need to manually call the getter