Many people have trouble getting the hang of dependency injection, at first. I think part of the problem is that it is actually so simple that we’re inclined to look for something more complicated. With that in mind, imagine that you’re writing an app that gives weather reports. You need a cloud-service (excuse the pun) to provide the data. At first, you go for a free weather report provider, but in future you’d like to integrate a weather service with better accuracy and more features. So like a good object-oriented developer, you make a WeatherClient protocol and back it initially with an implementation based on the free, online data provider.
It might look like this:
class WeatherReportController : UIViewController {
let weatherClient = WeatherReportClient(key: myApiKey)
}
A problem with this approach is if you wanted to change to another weather client implementation you’d have to go and find all the places in your code that use the old one, and move them over to the new one. Each time, making sure to pass in the correct initialization parameters. A common approach is to have a centrally configured singleton:
class WeatherReportController : UIViewController {
let weatherClient = WeatherReportClient.shared
}
This gets around the problem of having a single point of truth for configuration, but in order to test the WeatherReportController, you now have to test its collaborating class (the weather client) at the same time. This can get tricky, especially as your application gets more complex. Imagine testing Class A, depends on Class B, depends on Class C, depends on… Not much fun!
Rather than seek out collaborators, they are provided externally.
class WeatherReportController : UIViewController {
private(set) var weatherClient: WeatherReportProvider
init(client: weatherClient) {
self.weatherClient = client
}
}
More or less (dependency injection is, as they say, a $25 dollar term, for a five cent concept) but let‘s look at what happens when we start to apply this approach. If we continue to replace internally resolved dependencies with ones that are provided externally, we arrive at a module where the key actors, their lifecycles and their interactions are defined. This is known as the assembly or composition root.
Pulling construction of these components to the composition root lets your application tell an architectural story. When the key actors are pulled up into an assembly the app's configuration is no longer fragmented, duplicated or tightly coupled. We can have the benefits of singletons, without the drawbacks.
Minimal runtime-only library that works with pure Swift (structs, classes, protocols).
Supports Objective-C derived classes, when necessary.
Easy to install. Works on iOS, Mac & Linux. No compile-time weaving required.
Type safe. No reflection or ObjC runtime - uses higher-order functions.
Lifecycles management: Emits built instances with shared, weakShared, objectGraph & unshared scopes.
Simple and flexible. For example, it is easy to have multiple injectable instances that conform to the same protocol.
Provides the notion of a composition root in which key actors and their interactions are defined.
Successor to Typhoon for Objective-C and based on the excellent FieryCrucible.
Mac and iOS dev @appsquickly. Graph databases and network science @liberation_data. Open-source veteran.
You're a little intimidating, what with the RBF and those arcane spells and all. And you're way smarter than me. You're on my team!
Life is death. Death is Life. Teach the deserving. Teach with Passion. You will guide us.
In all honesty, you're a pretty freaky guy. None of your jokes make any sense. But you get stuff done. We need you and you got the job, Shaman.
We're putting the band back together. People wonder if the Bard adds all that much. You and I know that none of this would be possible without you.
A. Yes.
Please send me a tweet @doctor_cerulean and I will
answer as best as I can.
A.Pilgrim is released under the MIT Software License.
A. Nothing! It is free. Just enjoy it.