Dependency injection in Swift
Tags: dependencyinjection, swift, ios
Published: 2021-06-07 08:49:00

---
title: Dependency injection in Swift
published: true
date: 2021-05-28 14:31:56 UTC
tags: dependencyinjection,swift,ios
canonical_url: 
---

![](https://cdn-images-1.medium.com/max/1024/1*V8dCfxxnzQSK8PGA0J0itw.png)

#### How to write self injecting code?

Hey there, I hope you’re having a wonderful day :)

In this article, we will demonstrate on how to minimize the amount of work needed to setup class instance definition and dependency injection inside your iOS project.

I will assume you already know what dependency injection is and why is it an integral part of a testable architecture, but if not, I highly recommend taking a look at various sources in order to understand it before continuing.

### **#1 Define classes and dependencies**

In this example, we will create a simple shopping app where a user is able to select items from a list of products, add them to a shopping cart and then perform a purchase. This project will consist of 4 main components:

- A service for performing mocked API calls
- A repository for providing the data
- Use cases for fetching items and buying
- UI presenting the data to the user

Notice how we first started with the service. This is because the service will be at the top of our dependency graph. In other words, our service will have no dependencies on its own, but components bellow it will have a dependency on a component above it.

In a real world however, a service would probably also have a dependency on some network provider for performing the API calls, but we will keep this example as simple as possible, by having mocked data provided by the service.

So let’s define our **Service** protocol and class implementation:

```
import RxSwift

protocol ShopService {
    func getProducts() -> Single<[Product]>
    func purchaseProducts(from shoppingCart: ShoppingCart) -> Single<Bool>
}

final class ShopServiceImpl: ShopService {
    
    func getProducts() -> Single<[Product]> {
        return .just(Product.allCases)
    }
    
    func purchaseProducts(from shoppingCard: ShoppingCart) -> Single<Bool> {
        return .just(true)
    }
}
```	


Our service is pretty simple and straightforward. We will keep the implementation at a minimum, since that part is irrelevant for the topic of dependency injection.

Next we will define the **Repository** which has a dependency on our service:

```
import RxSwift

protocol ShopRepository {
    func getProducts() -> Single<[Product]>
    func purchaseProducts(from shoppingCart: ShoppingCart) -> Single<Bool>
}

final class ShopRepositoryImpl: ShopRepository {
    
    private let service: ShopService
    
    init(service: ShopService) {
        self.service = service
    }
    
    func getProducts() -> Single<[Product]> {
        return service.getProducts()
    }
    
    func purchaseProducts(from shoppingCart: ShoppingCart) -> Single<Bool> {
        return service.purchaseProducts(from: shoppingCart)
    }
}	
```	

Our repository almost seems redundant, because it just calls the service methods. But that is fine, since it keeps our architecture consistent and clean. In a real life scenario, a repository would probably also perform some data mapping from API resource format to a domain format and/or save data to a local storage. Anyway, back to business.

Let’s define our use cases:

```
import RxSwift

protocol GetProductsUseCase {
    func execute() -> Single<[Product]>
}

final class GetProductsUseCaseImpl: GetProductsUseCase {
    
    private let repository: ShopRepository
    
    init(repository: ShopRepository) {
        self.repository = repository
    }
    
    func execute() -> Single<[Product]> {
        return repository.getProducts()
    }
}
```

```
import RxSwift

protocol PurchaseProductsUseCase {
    func execute(shoppingCart: ShoppingCart) -> Single<Bool>
}

final class PurchaseProductsUseCaseImpl: PurchaseProductsUseCase {
    
    private let repository: ShopRepository
    
    init(repository: ShopRepository) {
        self.repository = repository
    }
    
    func execute(shoppingCart: ShoppingCart) -> Single<Bool> {
        return self.repository.purchaseProducts(from: shoppingCart)
    }
}
```

And lastly, our view model to which we will bind our view:

```
import RxCocoa

class ShopVM {
    
    // Dependencies
    private let getProductsUseCase: GetProductsUseCase
    private let purchaseProductsUseCase: PurchaseProductsUseCase
    private let mapper: ShopViewMapper
    
    // Stored data
    private var selectedProducts: SelectedProducts = []
    
    init(getProductsUseCase: GetProductsUseCase,
         purchaseProductsUseCase: PurchaseProductsUseCase,
         mapper: ShopViewMapper) {
        
        self.getProductsUseCase = getProductsUseCase
        self.purchaseProductsUseCase = purchaseProductsUseCase
        self.mapper = mapper
    }
}

extension ShopVM: ViewModelType {
    
    typealias SelectedProducts = Set<Product>
    
    struct Input {
        let selectProduct: Driver<Product>
        let purchase: Driver<Void>
    }
    
    struct Output {
        let productList: Driver<[Product]>
        let selectedProducts: Driver<SelectedProducts>
        let totalPrice: Driver<String>
    }
    
    func transform(input: Input) -> Output {
        
        let productList = self.getProductsUseCase.execute()
            .asDriver(onErrorJustReturn: [])
        
        let selectedProducts = input.selectProduct
            .map { [unowned self] product -> SelectedProducts in
                
                // If product is already added to the shopping cart, tapping it again will remove it from the list
                if self.selectedProducts.contains(product) {
                    self.selectedProducts.remove(product)
                } else {
                    self.selectedProducts.insert(product)
                }
                
                return self.selectedProducts
            }
        
        let purchaseResult = input.purchase
            .withLatestFrom(selectedProducts)
            .asObservable()
            .map(mapper.mapShoppingCart)
            .flatMapLatest(purchaseProductsUseCase.execute)
            .map { [weak self] success -> SelectedProducts in
                guard let self = self else {
                    return []
                }
                if success {
                    self.selectedProducts.removeAll()
                }
                return self.selectedProducts
            }
            .asDriver(onErrorJustReturn: [])
        
        let selectedProductsMerge = Driver.merge(selectedProducts, purchaseResult)
        
        let totalPrice = selectedProductsMerge
            .map(mapper.mapTotalPrice)
        
        return .init(
            productList: productList,
            selectedProducts: selectedProductsMerge,
            totalPrice: totalPrice
        )
    }
}
```

```
import Foundation

protocol ShopViewMapper {
    func mapShoppingCart(from selectedProducts: ShopVM.SelectedProducts) -> ShoppingCart
    func mapTotalPrice(from selectedProducts: ShopVM.SelectedProducts) -> String
}

final class ShopViewMapperImpl: ShopViewMapper {
    
    func mapShoppingCart(from selectedProducts: ShopVM.SelectedProducts) -> ShoppingCart {
        //let products = selectedProducts.map { $1 }
        
        return .init(
            id: UUID().uuidString,
            products: Array(selectedProducts)
        )
    }
    
    func mapTotalPrice(from selectedProducts: ShopVM.SelectedProducts) -> String {
        let price = selectedProducts.reduce(0) { $0 + $1.pricePerKg * $1.averageWeight }
        let formattedPrice = price.formatPrice ?? ""
        return "Total price: \(formattedPrice)"
    }
}
```


Our final dependency graph looks like this:
![image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/wmgx893n29avrtvprea3.png)

Components that are in the same row are part of the same architectural layer. Our **ShopVC** is the view, but we don’t care about its implementation, so we have omitted it from the example. You can find the full implementation in the git repo link bellow. Notice how each of our implementation classes ends with a “Impl” suffix after the protocol name. This will be very important later on.

### **#2: Resolving class instances**

Now that we have defined our classes with their corresponding dependencies, how do we instantiate them?

First of, we want our **ShopRepository** and **ShopService** to be shared centralized data providers (singletons). A common way to achieve this is by defining a shared instance like this:

```
extension ShopServiceImpl {
    static let shared: ShopService = ShopServiceImpl()
}

extension ShopRepositoryImpl {
    static let shared: ShopRepository = ShopRepositoryImpl(service: ShopServiceImpl.shared)
}
```


We can already see several problems with this approach:

- A developer may not know which implementation to use as a dependency to some other class. He might create an instance manually without knowing there is a shared instance that should be used instead.
- A developer shouldn’t care how to instantiate some class. He should be provided an already created instance with its dependencies ready to be used, if possible.
- We are manually defining shared instances for each of our singletons, which is tedious work and creates boiler plate code. For big projects which has tons of singletons, this also affects maintainability.

Ignoring these problems for now, let’s see how would we instantiate our **ShopVM** view model:

```
let shopRepository: ShopRepository = ShopRepositoryImpl.shared
let getProductsUseCase: GetProductsUseCase = GetProductsUseCaseImpl(repository: shopRepository)
let purchaseProductsUseCase: PurchaseProductsUseCase = PurchaseProductsUseCaseImpl(repository: shopRepository)
let shopMapper: ShopViewMapper = ShopViewMapperImpl()
        
let shopViewModel = ShopVM(getProductsUseCase: getProductsUseCase,
                           purchaseProductsUseCase: purchaseProductsUseCase,
                           mapper: shopMapper)
```

Now we can see a lot more problems. Every time we need a new instance of the view model, we need to instantiate all of its non-singleton dependencies as well. Luckily, we can use a dependency injection framework to provide us with the instances, without caring about their dependencies. One of the most popular DI frameworks for Swift is [**Swinject**](https://github.com/Swinject/Swinject) due to being very lightweight and easy-to-use. Using Swinject, we can register our protocol implementations and dependencies as follows:

```
import Swinject

enum SingletonContainer {
    
    static let instance: Container = {
        let container = Container(defaultObjectScope: .container)
        
        container.register(ShopService.self) { _ in
            ShopServiceImpl()
        }
        
        container.register(ShopRepository.self) {
            ShopRepositoryImpl(service: $0.resolve(ShopService.self)!)
        }
        
        return container
    }()
}

enum InstanceContainer {
    
    static let instance: Container = {
        let container = Container(parent: SingletonContainer.instance, defaultObjectScope: .transient)
        
        container.register(GetProductsUseCase.self) {
            GetProductsUseCaseImpl(repository: $0.resolve(ShopRepository.self)!)
        }
        
        container.register(PurchaseProductsUseCase.self) {
            PurchaseProductsUseCaseImpl(repository: $0.resolve(ShopRepository.self)!)
        }
        
        container.register(ShopViewMapper.self) { _ in
            ShopViewMapperImpl()
        }
        
        container.register(ShopVM.self) {
            ShopVM(getProductsUseCase: $0.resolve(GetProductsUseCase.self)!,
                   purchaseProductsUseCase: $0.resolve(PurchaseProductsUseCase.self)!,
                   mapper: $0.resolve(ShopViewMapper.self)!)
        }
        
        return container
    }()
}
```

This is much better, because we can now retrieve a new instance of the **ShopVM** without caring about its dependencies by just calling:

```
let shopViewModel = InstanceContainer.instance.resolve(ShopVM.self)!
```

And we no longer need the _shared_ instances for **ShopRepository** and **ShopService** singletons, since they are now registered in a shared singleton container.

This is a huge improvement, however we still have to write boiler plate code for registering our instances to the containers. Also, if we add/remove/change a dependency in the class’ constructor, we also have to update the code in the container it was registered in. For big projects, the container would grow huge and become hard to maintain. We should delegate writing this boiler plate code to the compiler, using a code generator like [**Sourcery**](https://github.com/krzysztofzablocki/Sourcery) **.**

### #3 Writing self injecting code

For starters, let’s add a generic type extension to **Swinject** ’s Resolver _resolve()_ method, so we don’t have to explicitly specify a type when resolving an instance:

```
import Swinject

extension Resolver {
    
    func resolve<T>(type: T.Type = T.self) -> T {
        guard let instance = self.resolve(T.self) else {
            fatalError("Implementation for type \(T.self) not registered to \(self).")
        }
        return instance
    }
}
```

We can now resolve an instance by simply calling _.resolve()_ if type can be inferred. This will make it slightly easier to write the code generation script.

Next, integrate **Sourcery** into the project. I will assume you already know what **Sourcery** is and how to use it by following the [documentation](https://github.com/krzysztofzablocki/Sourcery). In short, it’s a code generator for Swift which saves your from hassle of writing boiler plate or repetitive code by letting the compiler do it for you. If you aren’t already using code generators in your projects, you should definitely start.

With that out of the way, let’s write a Sourcery **template** file that will generate containers for registering our types and performing dependency injections. We will then add a build phase run script that will execute Sourcery and generate our code whenever we build the project.

But before it can do that, we need to tell Sourcery which types we want to be injectable and which types we want as singletons. So let’s define two blank protocols: _Injectable_ and _Singleton._

```
protocol Injectable {}

protocol Singleton {}
```


Then make all our types conform to _Injectable type._ On top of that, make our **ShopRepository** and **ShopService** conform to _Singleton_ type. Our **ShopService** protocol now looks like this:

```
protocol ShopService: Injectable, Singleton {
    func getProducts() -> Single<[Product]>
    func purchaseProducts(from shoppingCart: ShoppingCart) -> Single<Bool>
}
```

Now the fun part. We will write a Sourcery template file which will scan our code for _Injectable_ and _Singleton_ types, register them in respective containers and then resolve their implementation class. So **PurchaseProductsUseCase** will be resolved as **PurchaseProductsUseCaseImpl**. The script will then scan constructor parameters of the implementation class for any _Injectable_ type and perform constructor injections. And if it finds a _non-injectable_ type in the constructor parameters, it will throw a compiler error.

In other words, we want Sourcery to scan this:

```
protocol GetProductsUseCase: Injectable {
    func execute() -> Single<[Product]>
}
```

…to generate this:

```
container.register(GetProductsUseCase.self) {
      GetProductsUseCaseImpl(
          repository: $0.resolve()
      )
}
```

Seems easy enough. So let’s start by writing a macro that will generate constructor injection code:

```
{% macro injectType type %}
    {% if type.initializers.count == 0 %}
            {{ type.name }}()
    {% else %}
        {% for initializer in type.initializers %}
            {{ type.name }}(
                {% for parameter in initializer.parameters %}
                    {% if parameter.type.based.Injectable %}
                {{ parameter.name }}: resolver.resolve(){% if not forloop.last%}, {% endif %}
                    {% else %}
                #error("Cannot inject non-injectable dependency '{{ parameter.name }}' of type '{{ parameter.unwrappedTypeName }}'")
                    {% endif %}
                {% endfor %}
            )
        {% endfor %}
    {% endif %}
{% endmacro %}
```

There is a lot going on here, so let’s go step by step:

- We give this macro a name so we can reuse it from several places
- Then we check whether this type’s constructor has any initializers; if not then we just call the init method
- If the constructor has initializers, we iterate through them and check whether each initializer is of type _Injectable_, otherwise place a compiler error in place
- call .resolve() method for each parameter to resolve and inject its instance
- We need to append a comma after each parameter until the last one, to avoid syntax issues

If you call this macro for **ShopVM** for example, you would get this:

```
ShopVM(
    getProductsUseCase: resolver.resolve(), 
    purchaseProductsUseCase: resolver.resolve(), 
    mapper: resolver.resolve()
)
```

Works like magic! That’s why it’s called **Sourcery**  :)

This code won’t compile, of course, because we are missing a reference to the _resolver_. So let’s continue by adding stencil code for registering our **ShopVM** class to the container:

```
{% macro registerClass type %}
        // MARK: {{ type.name }}
        container.register({{ type.name }}.self) { resolver in
    {% call injectType type %}
        }
{% endmacro %}
```

Running our template script now will give us this:

```
// MARK: ShopVM
container.register(ShopVM.self) { resolver in
     ShopVM(
          getProductsUseCase: resolver.resolve(), 
          purchaseProductsUseCase: resolver.resolve(), 
          mapper: resolver.resolve()
     )
}
```	


There is one last piece of the puzzle missing: the container itself.

So let’s expand our template and add the stencil script for generating our Instance and Singleton containers:

```	
/// Provides singletons
enum SingletonContainer {

    static let instance: Container = {
        let container = Container(defaultObjectScope: .container)

{% for type in types.protocols where type.based.Injectable and type.based.Singleton %}
    {% call registerProtocol type %}

{% endfor %}
        return container
    }()
}

/// Provides new instances
enum InstanceContainer {

    static let instance: Container = {
        let container = Container(parent: SingletonContainer.instance, defaultObjectScope: .transient)

{% for type in types.protocols where type.based.Injectable and not type.based.Singleton %}
        {% call registerProtocol type %}

{% endfor %}

{% for type in types.classes where type.based.Injectable and not type.implements.Singleton %}
    {% for inheritedType in type.inheritedTypes %}
        {% if inheritedType == "Injectable" %}
            {% call registerClass type %}
        {% endif %}
    {% endfor %}
{% endfor %}

        return container
    }()
}
```	

Notice that when registering classes we need to make sure only classes that directly implement _Injectable_ type are included, because classes like **GetProductsUseCaseImpl** are already registered via _registerProtocol_ macro. Which we are missing still, so let’s add it:

```
{% macro registerProtocol type %}
        // MARK: {{ type.name }}
        container.register({{ type.name }}.self) { resolver in
    {% for impl in types.implementing[type.name] where impl.name|contains:"Impl" %}
        {% call injectType impl %}
    {% endfor %}
        }
{% endmacro %}
```

As you can see, this simply works by iterating through all types which implement the current protocol and contain “_Impl_” in their name. This will make sure we can have as many classes conform to the same protocol as we want in our project, but only one will be registered to and provided by the container.

Our final stencil template for generating dependency injection code looks like this:

```
import Swinject

{% macro injectType type %}
    {% if type.initializers.count == 0 %}
            {{ type.name }}()
    {% else %}
        {% for initializer in type.initializers %}
            {{ type.name }}(
                {% for parameter in initializer.parameters %}
                    {% if parameter.type.based.Injectable %}
                {{ parameter.name }}: resolver.resolve(){% if not forloop.last%}, {% endif %}
                    {% else %}
                #error("Cannot inject non-injectable dependency '{{ parameter.name }}' of type '{{ parameter.unwrappedTypeName }}'")
                    {% endif %}
                {% endfor %}
            )
        {% endfor %}
    {% endif %}
{% endmacro %}

{% macro registerProtocol type %}
        // MARK: {{ type.name }}
        container.register({{ type.name }}.self) { resolver in
    {% for impl in types.implementing[type.name] where impl.name|contains:"Impl" %}
        {% call injectType impl %}
    {% endfor %}
        }
{% endmacro %}

{% macro registerClass type %}
        // MARK: {{ type.name }}
        container.register({{ type.name }}.self) { resolver in
    {% call injectType type %}
        }
{% endmacro %}

/// Provides singletons
enum SingletonContainer {

    static let instance: Container = {
        let container = Container(defaultObjectScope: .container)

{% for type in types.protocols where type.based.Injectable and type.based.Singleton %}
    {% call registerProtocol type %}

{% endfor %}
        return container
    }()
}

/// Provides new instances
enum InstanceContainer {

    static let instance: Container = {
        let container = Container(parent: SingletonContainer.instance, defaultObjectScope: .transient)

{% for type in types.protocols where type.based.Injectable and not type.based.Singleton %}
        {% call registerProtocol type %}

{% endfor %}

{% for type in types.classes where type.based.Injectable and not type.implements.Singleton %}
    {% for inheritedType in type.inheritedTypes %}
        {% if inheritedType == "Injectable" %}
            {% call registerClass type %}
        {% endif %}
    {% endfor %}
{% endfor %}

        return container
    }()
}
```

Running the script will generate the following code:

```
// Generated using Sourcery 1.3.4 — https://github.com/krzysztofzablocki/Sourcery
// DO NOT EDIT
import Swinject




/// Provides singletons
enum SingletonContainer {

    static let instance: Container = {
        let container = Container(defaultObjectScope: .container)

        // MARK: ShopRepository
        container.register(ShopRepository.self) { resolver in
            ShopRepositoryImpl(
                service: resolver.resolve()
            )
        }

        // MARK: ShopService
        container.register(ShopService.self) { resolver in
            ShopServiceImpl()
        }

        return container
    }()
}

/// Provides new instances
enum InstanceContainer {

    static let instance: Container = {
        let container = Container(parent: SingletonContainer.instance, defaultObjectScope: .transient)

        // MARK: GetProductsUseCase
        container.register(GetProductsUseCase.self) { resolver in
            GetProductsUseCaseImpl(
                repository: resolver.resolve()
            )
        }

        // MARK: PurchaseProductsUseCase
        container.register(PurchaseProductsUseCase.self) { resolver in
            PurchaseProductsUseCaseImpl(
                repository: resolver.resolve()
            )
        }

        // MARK: ShopViewMapper
        container.register(ShopViewMapper.self) { resolver in
            ShopViewMapperImpl()
        }


        // MARK: ShopVM
        container.register(ShopVM.self) { resolver in
            ShopVM(
                getProductsUseCase: resolver.resolve(), 
                purchaseProductsUseCase: resolver.resolve(), 
                mapper: resolver.resolve()
            )
        }

        return container
    }()
}
```

And that’s it. You no longer ever need to worry about managing your instances, dependencies and singletons. You just add a dependency to the initializer of a _Injectable_ class and let **Sourcery** do the rest. Not only did we add a template for letting Sourcery generate dependency injections for us, but we also learned how to write our own stencil templates.

Hope this was helpful! You can find the full demo project in the GitHub repo below:

[dinocata/dependency-injection-demo](https://github.com/dinocata/dependency-injection-demo)

* * *