Today we will take a deep look at Dependency Injection in Swift, one of the most important techniques in software development and a heavily used concept in many programming languages. Specifically, we’ll explore which strategies/patterns we can use, including the Service Locator pattern in Swift.
The intent behind Dependency Injection is to decouple objects by having one object supplying the dependencies of another object. It is used to provide different configurations to modules and is especially useful for providing mocked dependencies for (unit-)testing your modules and/or applications.
We will use the term Dependency Injection in this article only as the design pattern that describes how one object can supply dependencies to other objects. Don’t confuse Dependency Injection with frameworks or libraries that help you injecting your dependencies.
Why should I use it?
Dependency Injection helps us to make our components less coupled and more reusable in different contexts. Basically, it is one form of Separation of Concerns, because it separates the algorithm using the dependencies from their initialization and configuration. To achieve that, we can apply different techniques to inject dependencies into our modules.
As mentioned above, one really important aspect of Dependency Injection is that it makes our code more testable. We can inject mock instances for the dependencies of our classes/modules we want to test. This allows us to focus our tests on the unit of code in the module and make sure that this part is working as expected, without having fuzzy side effects that lead to unclear test failures, because one of its dependencies is not behaving as expected. These dependencies should be tested on their own, to find the real mistakes easier and speed up your development workflow.
We described our testing strategies already in one of our previous articles. Make sure to read that article if you want to learn more about our testing setup.
Additionally, Dependency Injection allows us to bypass one of the most common mistakes in software development: Over- and misuse of singletons in your codebase. If you want to read more about why singletons are (often) bad, take a look at articles like that or that.
Different strategies to do Dependency Injection in Swift
There are a lot of ways to use Dependency Injection when working on your Swift codebase. Most principles also apply to other programming languages, even though in most other environments (especially in the Java community) people tend to use special Dependency Injection frameworks to do the heavy lifting for them.
And yes, there exist also Dependency Injection frameworks for Swift out there like Swinject or XServiceLocator. But today we are going to show you some simple tricks to inject your dependencies without introducing another third-party framework.
Initializer-based Dependency Injection
The most common way to inject dependencies in Swift is by passing the dependencies as parameters to the init functions and then storing them as member variables of your classes. This method is called Initializer-based Dependency Injection.
To see that technique in action let’s take a look at a short example of a service class that uses a repository object to fetch the data.
We inject a repository to our
BasketService so that our service doesn’t need to know how the articles used are provided. They could come from a repository that fetches the data from a local JSON file, or retrieved from a local database, or even fetched from a network service.
This allows us to use our
BasketService in different contexts and if we want to write tests for this class, we can inject mocked versions of our repository, to make our tests more predictable by using always the same test data.
Ok perfect, we were able to inject a mocked version of our repository with one dummy article to check if our service works as expected and adds our test article to the provided basket.
Property-based Dependency Injection
Ok, initializer-based dependency injection seems to be a good solution, but there are cases where it isn’t the right fit, for example in ViewControllers where it’s not so easy to work with initializers, especially if you use XIB or storyboard files.
We all know this error message and the annoying fix-it solution provided by Xcode. But how can use Dependency Injection without overriding all the default initializers?
That’s where property-based Dependency Injection comes into play. We assign properties of the module after initializing it.
Let’s see this in action for our
BasketViewController that has our
BasketService class as a dependency.
We were forced to use a force unwrapped optional here, to make sure that the app crashes when the
basketService property was not properly injected before.
If we want to get rid of the force unwrapped optional and provide default implementations we can declare the default assignee when declaring the properties.
Property-based Dependency Injection has some downsides as well: First, our classes need to handle the dynamic change of dependencies and second, we need to make the properties accessible and mutable from outside and can’t define them as private anymore.
Both solutions we have seen so far moved the responsibility of injecting the dependencies to the class creating the new module. This might be better than hardcoding dependencies into the modules, but moving this responsibility to own types is, in general, a better solution. It also makes sure, that we don’t need to have duplicate code for initializing modules across our codebase.
These types handle the creation of classes and set all their dependencies. These so-called Factory classes additionally solve the problem of passing dependencies around. We had to do this with all other solutions before and it can get messy if your classes have a large number of dependencies or you have multiple levels of dependencies like our example above: BasketViewController –> BasketService –> Repository.
Let’s take a look at a Factory for our Basket related classes.
By making the factory a protocol, we can have multiple implementations of it, for example a special factory for the test cases.
Factory-based Dependency Injection works hand in hand with the solutions we have seen before and allows us to mix different techniques, but keep a clear interface on how we create instances of our classes.
There is no better way to explain it, than by showing you an example:
DefaultBasketFactory implements the protocol defined above and has public factory methods and private ones. A factory method can and should use the other factory methods in the class for creating lower dependencies.
The example above shows perfectly how we combine the techniques of initializer-based and property-based Dependency Injection, but with the advantage of having an elegant and simple interface to create the dependencies.
To initialize an instance of our
BasketViewController we only need to write this single and self-explaining line of code.
The Service Locator Pattern
Based on the solutions we have seen so far we are going to build a more generalized and flexible solution using the so-called Service Locator design pattern. Let’s start by defining the involved entities for our Service Locator:
- Container: Stores the configuration on how to create instances of the registered types
- Resolver: Resolves the actual implementation for a type, by creating an instance of a class, using the configuration of the Container
- ServiceFactory: A generic factory solution for creating instances of the generic type
We start by defining a
Resolver protocol for the Service Locator Pattern. It’s a simple protocol with only one method for creating an instance conforming to the passed ServiceType type.
We can use objects conforming to that protocol in the following way:
Next up we define our
ServiceFactory protocol with an associated type ServiceType. Our factory will create instances of types conforming to the ServiceType protocol.
This looks quite similar to the Resolver protocol we have seen before, but it introduces the additional associated type for adding more type safety to our implementation.
Let’s define our first type conforming to this protocol called
BasicServiceFactory. This factory class uses the injected factory method to produce instances of classes/structs of type ServiceType. By passing the Resolver as an argument to the factory closure, we can use it to create lower level dependencies needed for creating instances of that type.
BasicServiceFactory struct could be used as a standalone and more generic solution than the Factory classes we have seen above. But we are not done yet. The last thing we need to implement the Service Locator Pattern in Swift is the Container.
Before we start writing our
Container class. Let’s repeat shortly what it should be doing for us:
- It should allow us to register new factories for a certain type
- It should store ServiceFactory instances
- It should be used as a Resolver for any stored type
To be able to store instances of ServiceFactory classes in a type-safe manner we would need to be able to have variadic generics implemented in Swift. This is not yet possible in Swift, but is part of the generic manifesto and will be added to Swift in a future version. In the meantime, we need to eliminate the generic type using a type erased version called
For sake of simplicity we won’t show you the implementation of it, but if you’re interested in it take a look at the full playground linked below.
Now we are going to create our
We define our Container as a struct acting as a resolver and storing the type erased factories. Next up we will add the code for the registration of new types with their factories.
The first method allows us to register a certain instance of a class for our ServiceType. This is especially useful for injecting singleton(like) classes such as
The second and even more important method creates a new factory and returns a new immutable container including that new factory.
The last missing piece is to actually conform to our
Resolver protocol and resolve instances using our stored factories.
We are using a guard statement to check if it contains a factory that is able to resolve our dependency and throw a fatal error otherwise. Finally, we return the instance created by the first factory supporting this type.
Usage of Service Locators
Let’s get to our basket example from before and define a container for all the basket related classes:
This shows already the power and elegance of our super simple solution. We can store all factories using chained register methods while using and mixing all the different Dependency Injection techniques we have seen before.
And last but not least our interface for creating instances stays simple and elegant.
We have seen different techniques to use Dependency Injection in Swift. More importantly, we have seen that you do not need to decide on one single solution. They can be mixed to get the combined strengths of every technique. To bring everything to the next level we introduced Factory classes and the more generic solution of the ServiceLocator pattern in Swift. This could be improved by adding additional support for multiple arguments or by adding more type safety when Swift introduces variadic generics.
For the sake of simplicity, we ignored things like scoping, dynamic dependencies and circular dependencies. All of these problems are solvable but out of scope for this article. You can see the full playground containing everything we have shown you today in action here. Alos, if you need a light-weight dependency injection library that still does most of the heavy lifting for you, check out our library XServiceLocator on GitHub.