13 min read

In software development, it’s always recommended to split the system into loosely coupled modules that can work independently as much as they can. Dependency Injection (DI) is a pattern that helps to reach this goal, creating a maintainable and testable system. It is often confused with complex and over-configurable frameworks that permit us to add DI to our code; in reality, it is a simple pattern that can be added without too much effort.

This article is taken from the book Hands-On Design Patterns with Swift by Florent Vilmart, Giordano Scalzo, and Sergio De Simone.  This book demonstrates how to apply design patterns and best practices in real-life situations, whether that’s for new or already existing Swift projects. You’ll begin with a quick refresher on Swift, the compiler, the standard library, and the foundation, followed by the Cocoa design patterns to follow up with the creational, structural, and behavioral patterns as defined by the GoF.  To follow along with the examples implemented in this article, you can download the code from the book’s GitHub repository.

In this article, we’ll see what Dependency Injection is, where it comes, and how it’s defined so that we can then discuss various methods to implement it, having a clear understanding of its principles.

Dependency Injection, a primer

Dependency Injection is one of the most misunderstood concepts in computer programming. This is because the Dependency Injection borders are quite blurry and they could overlap with other object-oriented programming concepts.


Let’s start with a formal definition given by Wikipedia:

“In software engineering, Dependency Injection is a software design pattern that implements inversion of control for resolving dependencies.”

To be honest, this is not really clear: what is Inversion of Control? Why is it useful for resolving dependencies?

In procedural programming, each object interacts with all of its collaborators in a direct way and also instantiates them directly. In Inversion Of Control, this flow is managed by a third party, usually, a framework that calls the objects and receives notifications.

An example of this is an implementation of a UI engine. In a UI Engine, there are two parts: the Views and the Models part. The Views part handles all the interaction with the users, such as tapping buttons and rendering labels, whereas the Models part is responsible for business logic. Usually, the application code goes in the Models part, and the connections with the Views are done via callbacks that are called by the engine when the user interacts with a button or a text field.

The paradigm changes from an imperative style where the algorithm is a sequence of actions, like in do this then do that, to an event style, when the button is tapped then call the server. The control of the actions is thus inverted. Instead of being the model that does things, the model now receives calls.

Inversion of Control is often called Hollywood Principle.

The essence of this principle is, “Don’t call us, we’ll call you,” which is a response you might hear after auditioning for a role in Hollywood.

In procedural programming, the flow of the program is determined by the modules that are statically connected together: ContactsView talks to ContactsCoreData and  ContactsProductionRemoteService, and each object instantiate its next collaborator.
In Inversion of Control, ContactsView talks to a generic ContactsStore and a generic ContactsRemoteService whose concrete implementation could change depending on the context. If it is during the tests, an important role is played by the entity that manages how to create and connect all the objects together.

After having defined the concept of IoC, let’s give a simpler definition of DI by James Shore:

“Dependency Injection” is a 25-dollar term for a 5-cent concept. […] Dependency Injection means giving an object its instance variables. Really. That’s it.”

The first principle of the book Design Patterns by the Gang of Four is “Program to an interface, not an implementation” which means that the objects need to know each other only by their interface and not by their implementation.

After having defined how all the classes in software will collaborate with each other, this collaboration can be designed as a graph. The graph could be implemented connecting together the actual implementation of the classes, but following the first principle mentioned previously, we can do it using the interfaces of the same objects: the Dependency Injection is a way of building this graph passing the concrete classes to the objects.

Four ways to use Dependency Injection

Dependency Injection is used ubiquitously in Cocoa too, and in the following examples, we’ll see code snippets both from Cocoa and typical client-side code. Let’s take a look at the following four sections to learn how to use Dependency Injection.

Constructor Injection

The first way to do DI is to pass the collaborators in the constructor, where they are then saved in private properties. Let’s have as an example on e-commerce app, whose Basket is handled both locally and remotely.
The BasketClient class orchestrates the logic, saves locally in BasketStore, and synchronizes remotely with BasketService:

protocol BasketStore {
    func loadAllProduct() -> [Product]
    func add(product: Product)
    func delete(product: Product)
}
protocol BasketService {
func fetchAllProduct(onSuccess: ([Product]) -> Void)
func append(product: Product)
func remove(product: Product)
}

struct Product {
let id: String
let name: String
//...
}

Then in the constructor of BasketClient, the concrete implementations of the protocols are passed:

class BasketClient {
    private let service: BasketService
    private let store: BasketStore
    init(service: BasketService, store: BasketStore) {
        self.service = service
        self.store = store
    }
func add(product: Product) {
store.add(product: product)
service.append(product: product)
calculateAppliedDiscount()
//...
}
// ...
private func calculateAppliedDiscount() {
// ...
}
}

In Cocoa and Cocoa Touch, the Apple foundation libraries, there are a few examples of this pattern.

A notable example is NSPersistentStore in CoreData:

class NSPersistentStore: NSObject {
    init(persistentStoreCoordinator root: NSPersistentStoreCoordinator?,
         configurationName name: String?,
         URL url: NSURL,
         options: [NSObject: AnyObject]?)
var persistentStoreCoordinator: NSPersistentStoreCoordinator? { get }
}

In the end, Dependency Injection as defined by James Shore is all here: define the collaborators with protocols and then pass them in the constructor. This is the best way to do DI. After the construction, the object is fully formed and it has a consistent state. Also, by just looking at the signature of init, the dependencies of this object are clear.

Actually, the Constructor Injection is not only the most effective, but it’s also the easiest.
The only problem is who has to create the object graph? The parent object? The AppDelegate?
We’ll discuss that point in the Where to bind the dependencies section.

Property Injection

We have already agreed that Construction Injection is the best way to do DI, so why bother finding other methods? Well, it is not always possible to define the constructor the way we want. A notable example is doing DI with ViewControllers that are defined in storyboards. Given we have a BasketViewController that orchestrates the service and the store, we must pass them as properties:

class BasketViewController: UIViewController {
    var service: BasketService?
    var store: BasketStore?
// ...
}

This pattern is less elegant than the previous one:

  • The ViewController isn’t in the right state until all the properties are set
  • Properties introduce mutability, and immutable classes are simpler and more efficient
  • The properties must be defined as optional, leading to add question marks everywhere
  • They are set by an external object, so they must be writeable and this could potentially permit something else to overwrite the value set at the beginning after a while
  • There is no way to enforce the validity of the setup at compile-time

However, something can be done:

  • The properties can be set as implicitly unwrapped optional and then required in viewDidLoad. This is as a static check, but at least they are checked at the first sensible opportunity, which is when the view controller has been loaded.
  • A function setter of all the properties prevents us from partially defining the collaborator list.

The class BasketViewController must then be written as:

class BasketViewController: UIViewController {
    private var service: BasketService!
    private var store: BasketStore!
func set(service: BasketService, store: BasketStore) {
self.service = service
self.store = store
}

override func viewDidLoad() {
super.viewDidLoad()
precondition(service != nil, "BasketService required")
precondition(store != nil, "BasketStore required")
// ...
}
}

The Properties Injection permits us to have overridable properties with a default value. This can be useful in the case of testing.
Let’s consider a dependency to a wrapper around the time:

class CheckoutViewController: UIViewController {
    var time: Time = DefaultTime()
}
protocol Time {
func now() -> Date
}

struct DefaultTime: Time {
func now() -> Date {
return Date()
}
}

In the production code, we don’t need to do anything, while in the testing code we can now inject a particular date instead of always return the current time. This would permit us of testing how the software will behave in the future, or in the past.

A dependency defined in the same module or framework is Local. When it comes from another module or framework, it’s Foreign.

A Local dependency can be used as a default value, but a Foreign cannot, otherwise it would introduce a strong dependency between the modules.

Method Injection

This pattern just passes a collaborator in the method:

class BasketClient {
func add(product: Product, to store: BasketStore) {
store.add(product: product)
calculateAppliedDiscount()
//...
}
// ...
private func calculateAppliedDiscount() {
// ...
}
}

This is useful when the object has several collaborators, but most of them are just temporary and it isn’t worth having the relationship set up for the whole life cycle of the object.

Ambient Context

The final pattern, Ambient Context, is similar to the Singleton.

We still have a single instance as a static variable, but the class has multiple subclasses with different behaviors, and each static variable is writeable with a static function:

class Analytics {
    static private(set) var instance: Analytics = NoAnalytics()
    static func setAnaylics(analitics: Analytics) {
        self.instance = analitics
    }
    func track(event: Event) {
        fatalError("Implement in a subclass")
    }
}
class NoAnalytics: Analytics {
override func track(event: Event) {}
}

class GoogleAnalytics: Analytics {
override func track(event: Event) {
//...
}
}

class AdobeAnalytics: Analytics {
override func track(event: Event) {
//...
}
}

struct Event {
//...
}

This pattern should be used only for universal dependencies, representing some cross-cutting concerns, such as analytics, logging, and times and dates.

This pattern has some advantages. The dependencies are always accessible and don’t need to change the API.
It works well for cross-cutting concerns, but it doesn’t fit in other cases when the object isn’t unique.
Also, it makes the dependency implicit and it represents a global mutable state that sometimes can lead to issues that are difficult to debug.

DI anti-patterns

When we try to implement a new technique, it is quite easy to lose control and implement it in the wrong way. Let’s see then the most common anti-patterns in Dependency Injection.

Control Freak

The first one is pretty easy to spot: we are not using the Injection at all. Instead of being Injected, the dependency is instantiated inside the object that depends on it:

class FeaturedProductsController {
    private let restProductsService: ProductsService
init() {
self.restProductsService = RestProductsService(configuration: Configuration.loadFromBundleId())
}
}

In this example, ProductsService could have been injected in the constructor but it is instantiated there instead.

Mark Seeman, in his book Dependency Injection in .NET, Chapter 5.1 – DI anti-patterns, calls it Control Freak because it describes a class that will not relinquish its dependencies.

The Control Freak is the dominant DI anti-pattern and it happens every time a class directly instantiates its dependencies, instead of relying on the Inversion of Control for that. In the case of the example, even though the rest of the class is programmed against an interface, there is no way of changing the actual implementation of ProductsService and the type of concrete class that it is, it will always be RestProductsService. The only way to change it is to modify the code and compile it again, but with DI it should be possible to change the behavior at runtime.

Sometimes, someone tries to fix the Control Freak anti-pattern using the factory pattern, but the reality is that the only way to fix it is to apply the Inversion of Control for the dependency and inject it in the constructor:

class FeaturedProductsController {
    private let productsService: ProductsService
init(service: ProductsService) {
self.productsService = service
}
}

As already mentioned, Control Freak is the most common DI anti-pattern; pay particular attention so you don’t slip into its trap.

Bastard Injection

Constructor overloads are fairly common in Swift codebases, but these could lead to the Bastard Injection anti-pattern. A common scenario is when we have a constructor that lets us inject a Test Double, but it also has a default parameter in the constructor:

class TodosService {
    let repository: TodosRepository
init(repository: TodosRepository = SqlLiteTodosRepository()) {
self.repository = repository
}
}

The biggest problem here is when the default implementation is a Foreign dependency, which is a class defined using another module; this creates a strong relationship between the two modules, making it impossible to reuse the class without including the dependent module too.

The reason someone is tempted to write a default implementation it is pretty obvious since it is an easy way to instantiate the class just with TodoService() without the need of Composition Root or something similar. However, this nullifies the benefits of DI and it should be avoided removing the default implementation and injecting the dependency.

Service Locator

The final anti-pattern that we will explore is the most dangerous one: the Service Locator.
It’s funny because this is often considered a good pattern and is widely used, even in the famous Spring framework.

Originally, the Service Locator pattern was defined in Microsoft patterns & practices’ Enterprise Library, as Mark Seeman writes in his book Dependency Injection in .NET, Chapter 5.4 – Service Locator, but now he is advocating strongly against it. Service Locator is a common name for a service that we can query for different objects that were previously registered in it. As mentioned, it is a tricky one because it makes everything seem OK, but in fact, it nullifies all the advantage of the Dependency Injection:

let locator = ServiceLocator.instance
locator.register( SqlLiteTodosRepository(),
forType: TodosRepository.self)

class TodosService {
private let repository: TodosRepository

init() {
let locator = ServiceLocator.instance
self.repository = locator.resolve(TodosRepository.self)
}
}

Here we have a service locator as a singleton, to whom we register the classes we want to resolve. Instead of injecting the class into the constructor, we just query from the service. It looks like the Service Locator has all the advantages of Dependency Injection, it provides testability and extensibility since we can use different implementations without changing the client. It also enables parallel development and separated configuration from the usage.

But it has some major disadvantages. With DI, the dependencies are explicit; it’s enough to look at the signature of the constructor or the exposed properties to understand what the dependencies for a class are. With a Service Locator, these dependencies are implicit, and the only way to find them is to inspect the implementation, which breaks the encapsulation.

Also, all the classes are depending on the Service Locator and this makes the code tightly coupled with it. If we want to reuse a class, other then that class, we also need to add the Service Locator in our project, which could be in a different module and then adding the whole module as dependency where we wanted just to use one class. Service Locator could also give us the impression that we are not using DI at all because all the dependencies are hidden inside the classes.

In this article, we covered the different flavors of dependency injection and examines how each can solve a particular set of problems in real-world scenarios.

If you found this post useful, do check out the book, Hands-On Design Patterns with Swift. From learning about the most sought-after design patterns to comprehensive coverage of architectural patterns and code testing, this book is all you need to write clean, reusable code in Swift.

Read Next

Implementing Dependency Injection in Google Guice [Tutorial]

Implementing Dependency Injection in Spring [Tutorial]

Dagger 2.17, a dependency injection framework for Java and Android, is now out!