Types of Dependency Injection in Swift — with Testing Examples

Eduardo Sanches Bocato
The Startup
Published in
3 min readJan 26, 2020

Dependency Injection is an essential piece of knowledge for an iOS Developer nowadays. Therefore, it is very important to know how to implement it in Swift.

In this post, I won't explain deeply what it is or when to use it, but rather leave some material about it and provide some examples.

What is Dependency Injection?

In software engineering, dependency injection is a technique whereby one object supplies the dependencies of another object.
Source: Wikipedia

Reading recommendations:
- Inversion of Control Containers and the Dependency Injection pattern
- DIP in the Wild

What are the advantages of using it?

It is essential when you are aiming for a testable code. Instead of using Singletons or making the objects create their own dependencies, you receive them from outside using a dependency injection technique. This makes testing a lot easier since you can create Test Doubles to control inputs and verify outputs of your code.

Some advantages:

  • Reduced coupling.
  • Flexibility, making your application more configurable. This means that we can have many concrete implementations, but the interface won't change.
  • Code reusability.
  • Better testability.
  • Makes the code easier to maintain in the long run.

In this article, I will present examples, succinct explanation, some tests, and discussion of the types of injection below:

  • Initializer-based
  • Property-based
  • Parameter-based

Initializer Based

With this technique, you will provide the object with everything it needs on its initialization.

Let's assume you are creating an ImagesService that uses Cache and a Network layer. With the initializer-based technique, it could be something like this:

Intializer-based example.

What about the tests? How this could be used on them?

Intializer-based test example.

Notice that the usage of default parameters on the init method can simplify ImagesService in the application, while also making it simple to accept test doubles injection.

Property-based

It consists of exposing your dependency with a public or internal access modifier to enable it to be assigned from outside. Which can be an option anytime you don’t have control over the initialization of the subject needing the dependency.

Note: This could come in handy when you have a legacy application and is starting to separate responsibilities on new components or dependencies.

Consider that you have some legacy code that does not use injection techniques, where everything happens on the controller and uses storyboards. In order to make its logic testable, you could use the technique explained above as shown below:

Before using dependency injection.
After applying Property-based injection.

How could we test it?

Testing our legacy code with property-based injection.

Parameter-based

Consists on using a parameter to inject your dependencies.

This turns out to be very useful also when dealing with legacy code since you can simply inject the dependency needed as a parameter and use a default value. Using this approach is many times less expensive than modifying the whole class to enable initializer or property-based injection.

Consider that we have another implementation of the ImageService, from the first example, but now implemented with static methods like below:

ImageService implemented with static methods.

Since this static approach doesn't have initializers, it could be very painful for us to change it's implementation all over the code using initializer or property-based injection… Taking that into account, a possible approach could be extracting interfaces for Cache and Network, as we did on the initializer-based example, passing the dependencies as a parameter with a default value.
The code would be something like:

ImageService refactored to use parameter-based injection.

This would not change the ImageService usage along with the legacy code but will enable us to take control over Cache and Network for our tests.
The tests will end up being something like the example below:

Conclusion

Knowing different dependency injection techniques will help you decide which one, or which combination of them is more suitable to your problem in order to create more testable, reusable, and maintainable code.

--

--

Eduardo Sanches Bocato
The Startup

iOS Engineer, AI enthusiast, Crossfit/Gymnastics. Currently working as iOS Lead @adidas