I like dependency injection – it keeps your code simple and clear. Classes don't concern themselves with discovering, creating or managing their collaborators – they simply declare what they are, then go ahead and use them. They might not even know what class they are.
If you look around you'll find lots of dependency injection solutions for Swift, but they're often complex, verbose or both. Now, just because you 're writing lots of lines of code, it doesn't mean you're actually doing any work – simplicity and brevity win in my book, and I try to keep code that way wherever possible.
The simple way
Here's an approach to dependency injection that's almost trivially simple, but much of the time it's all you need.
Let's start with a class that predicts lottery numbers:
class LotteryPredictor {
func predictLotteryNumbers(on date: Date) -> [Int] {
// todo must get round to implementing this
}
}
Combine this with a ticket purchasing API and we could be onto something:
class LotteryApi {
init(url: URL) { ... }
func buyTicket(numbers: [Int], onFinished: (Error?)->Void) {
...
}
}
Those are our dependencies. Let's make them available in our app:
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
Dependencies.add(LotteryPredictor())
Dependencies.add(LotteryApi(url: ...))
...
}
and then make use of them:
class ViewController: UIViewController {
// lazy? See below.
private lazy var lotteryPredictor: LotteryPredictor = Dependencies.use()
private lazy var lotteryApi: LotteryApi = Dependencies.use()
@IBAction func makeMeRich() {
let numbers = lotteryPredictor.predictLotteryNumbers(on: Date())
lotteryApi.buyTicket(numbers: numbers) {
// carry on...
}
}
}
See how easy it is? One line to make a dependency available, one line to use it. No special hoops to jump through for view controllers. It's simple, elegant and – most importantly – readable. Even the "lazy" is probably unnecessary (read on for more about that).
Implementation of the dependencies system is pretty straightforward too:
public class Dependencies {
private static var dependencies = [Any]()
public static func add<T>(_ dep: T) {
dependencies.append(dep)
}
public static func use<T>() -> T {
guard let dependency: T = (dependencies.first { $0 is T } as? T) else {
preconditionFailure("no dependencies of type \(T.self)")
}
return dependency
}
}
That's it!
Yes, this is just a bunch of singletons. Told you it was simple, but remember – you'll often need nothing more, and nobody's going to come and give you points for using that elaborate framework you didn't really need. And it's better than writing stuff like this:
class LotteryApi {
static let instance = LotteryApi(url: ...)
...
}
...not least because it lets you do things like...
Testing
When we're writing tests, we probably want to use a fake api service instead of hitting the real one. We might have something like this:
protocol LotteryApi {
func buyTicket(numbers: [Int], onFinished: (Error?)->Void)
}
class RealLotteryApi: LotteryApi { ... }
class FakeLotteryApi: LotteryApi { ... }
Test implementation is easy – just make your dependencies available, substituting fake ones for real ones as appropriate:
func testWealthGenerator() {
// might move these to setUp(), or somewhere else
Dependencies.add(LotteryPredictor())
Dependencies.add(FakeLotteryApi())
let wealthGenerator = WealthGenerator()
// now test some stuff
}
Optional dependencies
Perhaps our lottery number predictor requires a modern device (predicting the future is hard), and so it isn't always available. We can enhance things slightly to allow optional dependencies.
Creation now looks like this:
if let lotteryPredictor = LotteryPredictor() {
Dependencies.add(lotteryPredictor)
}
and declaration like this:
lazy var lotteryPredictor: LotteryPredictor? = Dependencies.useOptional()
Implementation isn't much more complex:
public class Dependencies {
private static var dependencies = [Any]()
public static func add<T>(_ dep: T) {
dependencies.append(dep)
}
public static func use<T>() -> T {
guard let dep: T = useOptional() else {
preconditionFailure("no dependencies of type \(T.self)")
}
return dep
}
public static func useOptional<T>() -> T? {
dependencies.first { $0 is T } as? T
}
}
Lazy? Just for dependency cycles.
We're declaring our dependency variables as lazy above, but chances are this is unnecessary – it's just to cope with dependency cycles.
class Romeo {
var juliet: Juliet = Dependencies.use()
}
class Juliet {
var romeo: Romeo = Dependencies.use()
}
Dependencies.add(Romeo()) // oh dear
Dependencies.add(Juliet())
As written, we get a crash when we create Romeo because Juliet doesn't exist yet. A dependency cycle might be more complex (A → B → C → D → A) but you'd still have the same problem of what to create first. Using lazy variables solves that.
But such co-dependency is arguably a bad idea, and you probably don't have it. You could skip lazy altogether and just take care to create your objects in the right order.
But it's not even dependency injection!
Not technically, no – nothing's being injected as such.
But the point is it meets the goals of DI: classes declare their dependencies, but don't have to care about creation and management of objects – things that are none of their business.
Don't be afraid to choose a simple solution when it does what you need. Then you can get on and deliver some software!