Select Page

This tutorial has been updated for Xcode 9 and Swift 4.

My favorite quote about dependency injection is a quote by James Shore. It summarizes much of the confusion that surrounds dependency injection.

“Dependency Injection” is a 25-dollar term for a 5-cent concept. – James Shore

When I first heard about dependency injection, I figured it was a technique too advanced for my needs at that time. I could do without dependency injection, whatever it was.

What Is Dependency Injection

I later learned that, if reduced to its bare essentials, dependency injection is a simple concept. James Shore offers a succinct and straightforward definition of dependency injection.

Dependency injection means giving an object its instance variables. Really. That’s it. – James Shore

For developers new to dependency injection, it is important to learn the basics before relying on a framework or library. Start simple. Chances are that you already use dependency injection without realizing it.

Dependency injection is nothing more than injecting dependencies into an object instead of tasking the object with the responsibility of creating its dependencies. Or, as James Shore puts it, you give an object its instance variables instead of creating them in the object.

An Example

In the example below, we define a UIViewController subclass that declares a property, requestManager, of type RequestManager?.

import UIKit

class ViewController: UIViewController {

    var requestManager: RequestManager?

}

We can set the value of the requestManager property one of two ways.

Without Dependency Injection

The first option is to task the ViewController class with the instantiation of the RequestManager instance. We can make the property lazy or initialize the request manager in the view controller’s initializer. That is not the point, though. The point is that the view controller is in charge of creating the RequestManager instance.

import UIKit

class ViewController: UIViewController {

    var requestManager: RequestManager? = RequestManager()

}

This means that the ViewController class not only knows about the behavior of the RequestManager class. It also knows about its instantiation. That is a subtle but important detail.

With Dependency Injection

In the second option, we inject the RequestManager instance into the ViewController instance. Even though the end result may appear identical, it is not. By injecting the request manager, the view controller does not know how to instantiate the request manager.

// Initialize View Controller
let viewController = ViewController()

// Configure View Controller
viewController.requestManager = RequestManager()

Many developers immediately discard the second option because it is cumbersome and unnecessarily complex. But if you consider the benefits, dependency injection becomes more appealing.

Another Example

I would like to show you another example to emphasize the point I made in the previous section. Take a look at the following example.

protocol Serializer {

    func serialize(data: AnyObject) -> Data?

}
class RequestSerializer: Serializer {

    func serialize(data: AnyObject) -> Data? {
        ...
    }

}
class DataManager {

    var serializer: Serializer? = RequestSerializer()

}

The DataManager class has a property, serializer, of type Serializer?. In this example, Serializer is a protocol. The DataManager class is in charge of instantiating an instance of a type that conforms to the Serializer protocol, the RequestSerializer class in this example.

Should the DataManager class know how to instantiate an object of type Serializer? Take a look at the following example to show the power of protocols and dependency injection.

// Initialize Data Manager
let dataManager = DataManager()

// Configure Data Manager
dataManager.serializer = RequestSerializer()

The DataManager class is no longer in charge of instantiating the RequestSerializer class. It no longer assigns a value to its serializer property. In fact, we can replace RequestSerializer with another type as long as it conforms to the Serializer protocol. The DataMananger no longer knows or cares about these details.

What You Gain

I hope the above examples have at least captured your attention. Let me list a few additional benefits of dependency injection.

Transparency

By injecting the dependencies of an object, the responsibilities and requirements of a class or structure become more clear and more transparent. By injecting a request manager into a view controller, we understand that the view controller depends on the request manager and we can assume the view controller is responsible for request managing and/or handling.

Testing

Unit testing is so much easier with dependency injection. Dependency injection allows developers to replace an object’s dependencies with mock objects, making unit tests easier to set up and isolate behavior.

class MockSerializer: Serializer {

    func serialize(data: AnyObject) -> Data? {
        ...
    }

}
// Initialize Data Manager
let dataManager = DataManager()

// Configure Data Manager
dataManager.serializer = MockSerializer()

Separation of Concerns

As I mentioned and illustrated earlier, another subtle benefit of dependency injection is a stricter separation of concerns. The DataManager class in the above example isn’t responsible for instantiating the RequestSerializer instance. It doesn’t need to know how to do this.

Even though the DataManager class is concerned with the behavior of its serializer, it isn’t, and shouldn’t be, concerned with its instantiation. What if the RequestManager of the first example also has a number of dependencies. Should the ViewController be aware of those dependencies too?

Coupling

The example with the DataManager class illustrated how the use of protocols and dependency injection can reduce coupling in a project. Protocols are incredibly useful and versatile in Swift. This is one scenario in which protocols really shine.

Types

Most developers consider three forms or types of dependency injection:

  • dependency injection through an initializer
  • dependency injection using properties
  • dependency injection in methods

These types shouldn’t be considered equal, though. Let me list the pros and cons of each type.

Initializer

I personally prefer to pass dependencies during the initialization of an object because this has several key benefits. The most important benefit is that dependencies passed in during initialization can be made immutable. This is very easy to do in Swift by declaring the properties for the dependencies as constants. Take a look at the below example.

class DataManager {

    private let serializer: Serializer!

    init(serializer: Serializer) {
        self.serializer = serializer
    }

}
// Initialize Request Serializer
let serializer = RequestSerializer()

// Initialize Data Manager
let dataManager = DataManager(serializer: serializer)

The only way to set the serializer property is by passing it as an argument during initialization. The init(serializer:) method is the designated initializer and guarantees that the DataManager instance is correctly configured. Another benefit is that the serializer property cannot be mutated.

Because we are required to pass the serializer as an argument during initialization, the designated initializer clearly shows what the dependencies of the DataManager class are.

Internal or Public Properties

Dependencies can also be injected by declaring an internal or public property on the class or structure that requires the dependency. This may seem convenient, but it adds a loophole in that the dependency can be modified or replaced. In other words, the dependency isn’t immutable.

import UIKit

class ViewController: UIViewController {

    var requestManager: RequestManager?

}
// Initialize View Controller
let viewController = ViewController()

// Configure View Controller
viewController.requestManager = RequestManager()

Methods

Dependencies can also be injected whenever they are needed. This is easy to do by declaring a method that accepts the dependency as a parameter. In the example below, the serializer isn’t a property on the DataManager class. Instead, the serializer is injected as an argument of the serializeRequest(_:serializer:) method.

class DataManager {

    func serializeRequest(_ request: Request, with serializer: Serializer) -> Data? {
        ...
    }

}

Even though the DataManager class loses some control over the dependency, the serializer, this type of dependency injection introduces flexibility. Depending on the use case, we can choose what type of serializer to pass into serializeRequest(_:serializer:).

Singletons

Dependency injection is a pattern that can be used to eliminate the need for singletons in a project. I am not a fan of the singleton pattern and I avoid it whenever possible. Even though I don’t consider the singleton pattern an anti-pattern, I believe they should be used very, very sparingly. The singleton pattern increases coupling whereas dependency injection reduces coupling.

Too often, developers use the singleton pattern because it is an easy solution to a, often trivial, problem. Dependency injection, however, adds clarity to a project. By injecting dependencies during the initialization of an object, it becomes clear what dependencies the target class or structure has and it also reveals some of the object’s responsibilities.

What’s Next?

Dependency injection is one of my favorite patterns because it helps me stay on top of complex projects. This pattern has so many benefits. The only drawback I can think is the need for a few more lines of code. Questions? Leave them in the comments below or reach out to me on Twitter.