Pragmatic Dependency Injection in Swift - Part One

At Oak City Labs, we’re always trying to find ways to improve our code’s stability, testability and maintainability. Today we look at the problem of singletons, global state and how to avoid these landmines in the context of Swift.

The Problem

Let’s talk about global variables. Most everyone can agree that global variables are A Very Bad Thing. Globals are undoubtably a code smell and religiously avoided by experienced programmers. Globals are problematic for lots of reasons, but mostly because of Spooky Action At A Distance. If two pieces of code both access a global variable, they can interact via the global variable, often in unexpected and undesirable ways. In this article, Misko Hevery (the Angular guy) dives deep into the many ways this can go pear shaped quickly.

New developers sometimes try to fix global variables by switching to singletons and maintain state within a magic object that is just a static method call away. I’ve heard more than one developer exclaim “Oh no, we never use global variables. We use singletons instead.” The ugly truth though, is that singletons are really just fancier global variables. You still get Spooky Action. You still have implicit and hidden dependencies. Try to write a unit test on a class that relies on a singleton. You can’t look at the exposed interface for this class (think header file) and know about the secret, hidden dependence on that singleton. You must inspect the code and it’s a matter of tedious and confusing mocking of static class methods to try and make this testable. Then write a second test and discover that the tests fail depending on the order in which they run. It’s crazy making!

The most disappointing aspect of singletons is that Apple uses them so often in their own API — UIScreen, NSNotificationCenter, NSUserDefaults, etc. It seems as if they’re encouraging us to use singletons, even when it makes applications fragile and testing difficult.

The Solution — Dependency Injection

“Dependency Injection” (DI) sounds intimidating. And complicated. And scary. People write long, drawn out blog posts about it. But it’s not nearly as intimidating as you might think. At the core, DI just means that you explicitly supply a class with any dependencies, either through initialization or a method call.

Consider this class that uses a two singletons:

class SocialInfo {
    func connectionCount() {
        let twCount = TwitterApi.sharedManager.followerCount
        let fbCount = FaceBookApi.sharedManager.friendCount
        return twCount + fbCount
    }
}

The connectionCount() function returns the total number of connections on FaceBook and Twitter, using the singleton managers to access those two different APIs. But if we just look at the API, it’s not clear that SocialInfo depends on FaceBookApi and TwitterApi. For all we know, it’s valid to write

let count = SocialInfo().connectionCount()

but looking at the code for that class, we can tell that TwitterApi and FaceBookApi need to be set up and configured first. Imagine if this were a closed source library. We would have no way to know about these implementation details. And we shouldn’t have to care about implementation details anyway.

Now think about what it means to test the SocialInfo class. Our tests shouldn’t rely on network resources, so we’re going to have to mock those singletons. I’ve used several mocking libraries and these are always finicky at best. In Swift especially, the mocking libraries are quite young and this sort of thing is still difficult.

Now let’s consider the same class, rewritten a little for DI.

class SocialInfo {
    let twApi: TwitterApi
    let fbApi: FaceBookApi

    init(twitterApi: TwitterApi, facebookApi: FaceBookApi) {
        twApi = twitterApi
        fbApi = facebookApi
    }

    func connectionCount() {
        let twCount = twApi.followerCount
        let fbCount = fbApi.friendCount
        return twCount + fbCount
    }
}

With the updates, it’s clear that SocialIinfo depends on a valid instance of TwitterApi and FaceBookApi. Getting a connection count looks like

let count = SocialInfo(twitterApi, facebookApi).connectionCount()

It’s clear that we need to have a configured instance of the Twitter and Facebook API objects ready to pass in so that SocialIinfo can use them to connect with the services.

And testing is much easier too! Instead of having to build mock objects when can create FakeTwitterApi and FakeFaceBookApi objects that are subclasses of our real objects and just hardwired to return known values for the counts. This is really straightforward, readable, and doesn’t require any mocking.

So that’s dependency injection — simply the idea that dependencies are passed in explicitly rather than pulled out of the ether via some magical singleton or (gasp!) global variable.

Tune in next time for the exciting conclusion and the implementation of pragmatic dependency injection in Swift.