Dependency Injection in Swift — Part 1
Enabling acceptance tests in an easy way
When you are developing apps using the OOP paradigm, you will realize that, if you want to be able to test the software, you need to use IoC principle. Usually, to achieve this we use the dependency injection pattern which consists of providing each class with the dependencies it needs.
In this article I will show a way to put in place this pattern in Swift so that it is very easy to mock some dependencies of the graph.
Even if you don’t care about testing your apps I recommend you apply this principle in your code, you will appreciate it as the project gets bigger.
Using default values in constructors
In Swift, we can implement bastard injection by using default values in the constructor.
Easy, right? At first it may seem like a good idea because it improves testability and it’s easy to put in place, but it does present some problems. The main problem of this solution is that you are tightly coupled with the default constructor of your dependencies. Imagine that you want to write a UI test in which you use a test double to simulate an HTTP response. In that case, you would need to provide all the dependencies of this UIViewController because you can not use the default constructors any more. It’s a waste of time, isn’t it?
Dip & Swinject
Due to the problem discussed above, I decided to try some alternatives like Dip or Swinject. Both frameworks are actually very similar. They are dependency containers in which you can register how dependencies are resolved with a lambda. At high-level, what they do is store these lambdas in a dictionary and use it when you need to instantiate a service. So, to solve the problem you just need to override anything you want to replace with the test double.
However, these frameworks have some drawbacks such as:
- The dependency graph is built at runtime, so if you forget to register how to resolve a dependency the app will crash at runtime.
- Since you need to register dependencies before using them, loading the app will take longer if you do it at startup.
- You need to use force unwrap (Swinject) or try (Dip) when resolving dependencies.
- And the worst problem in my opinion is that if you need to pass arguments in runtime, these arguments are not typed and there is no autocomplete. It is very easy to make a mistake, a simple change in the order of dependencies and the app will crash at runtime.
Because of this, I decided not to use them and find a solution that suits my use case without affecting performance and without losing the security that we get thanks to the Swift compiler.
The solution I propose has the following features you will love:
- The arguments needed to resolve a dependency are typed, so you can’t make mistakes.
- If you forget to define how to instantiate an object, the app will not compile.
- The performance of the app is not affected. You don’t need to register how to resolve dependencies at runtime.
- Dealing with optional values is not uncomfortable as it can be in Swinject or Dip.
- You don’t need to use any external library. A dependency injector affects the entire codebase and having dependencies with third-party libraries is risky.
In order to achieve it, the solution uses some Swift features such as type inference and protocol extension. Let’s see a simple example.
Imagine the next struct that represents super heroes:
We could define a protocol with a function that provides you instances of a super hero and extend it with a default implementation. We will name it as
Now we can use it to resolve
And how can we mock it in our tests? Well, it’s really easy, we just need to create a new class conforming to
SuperHeroAssembler but with a different resolve function. Let see how we can achieve it:
Maybe with such a simple example you see no advantages, but this idea can be very useful if used well. Let’s look at an example of a more elaborate use case to see how this works.
How does it work with a use case?
Imagine a screen where you need to display all the contacts of a given user. Contacts come from network and we want to be able to use a test double in our acceptance tests.
To achieve this we use the following classes:
To define how the dependencies are resolved we will use an assembler for that use case, I will call it
ContactsSceneAssembler. In this assembler we will have a function able to resolve each Type needed.
As you can see, we can (and must) reuse the assembler itself to resolve our dependencies. Another great thing is that we know the input arguments of the function and its types at runtime. So, when you are writing autocomplete will be able to do most of the work :D.
Now, to create a
ContactsListViewController we just need to use the assembler to resolve it using a given user.
As we saw in the first example, it will be posible to use a test double instead of
NetworkContactsDataSource in an easy way. You just need to create an assembler in which you resolve
ContactsDataSource to your test double object:
In the following articles I will give some tips and good practices like:
- What happens if we want to use different dependencies on iPad?
- How can we use other assemblers to resolve our dependencies?
- Where should I create the assembler so I do not get coupled to it?
In the meantime, you can keep an eye on this repository where I’m using assemblers. It was migrated from Swift 2.3 to Swift 3 and still need some love in the naming.
If you like what you have read you can say hello on Twitter.