23 Mar, 2019
Dependency injection pattern in Swift
What it is for.
Decoupling Clients from their dependencies and separating the creation and configuration (if any) of said dependencies from the Client’s behavior.
Dependency injection is a technique that quite literally applies the “Dependency Inversion Principle” (DIP) that states:
- High-level modules should not depend on low-level modules. Both should depend on abstractions.
- Abstractions should not depend on details. Details should depend on abstractions.
How it goes.
Its initial requirement is the definition of Protocols (Interfaces) which describe the expected behavior of the dependencies without specifying their concrete implementation.
In the example above we create a Protocol for our dependency with one method
sayHelloWorld(). We haven’t committed to specifying how this will be implemented and this flexibility is one of the most important features of Dependency Injection.
Next, we can define a specific implementation.
HelloWorldConsole class implements
HelloWorldProtocol to define a concrete behavior for
Finally we can use this dependency in a Client class (in this case a
UIViewController ) where we will inject our
There are 2 very important things to note here:
- The instance property
helloWorldServiceis defined to be of the abstract type
HelloWorldProtocoland not the concrete type
This means that from this point on, both Dependency
HelloWorldConsole and Client
HelloWorldViewController are decoupled from each other and can be updated independently. Making changes to
HelloWorldConsole doesn’t require us to touch
HelloWorldViewController and vice versa.
HelloWorldViewControllerdoes not have any code to create or configure the
helloWorldService. It is instead delegating this responsibility to the caller of its constructor (the Injector).
The Injector code is now responsible for the creation of Client and Dependency. This powerful abstraction gives us the flexibility to easily change the concrete type of dependency.
For example, if we want to use another type of
HelloWorldProtocol type service, we could do the following:
And then we would modify the Injector code to use this other type of
Additionally, writing a unit test for the
HelloWorlViewController implementation becomes easy because we can create and inject a
HelloWorldProtocol dependency specific for testing (a Mock
So what’s the catch?
This type of Dependency Injection does has a few drawbacks that should be considered.
Checking the state of the dependencies
If you were paying attention to the example above, you might have noticed that the property
HelloWorldViewController is declared as implicitly unwrapped.
The Injector code uses then the convenience initializer
HelloWorldViewController(helloWorldService:) This is called Constructor injection.
But what would happen if we initialized
HelloWorldViewController using a constructor different than
Our Application would crash because we haven’t checked the state of our dependencies. Which brings us to the first disadvantage of Dependency Injection. Because the creation of the dependencies is delegated outside the Client, it is hard to guarantee that these have been indeed properly setup.
Depending on the situation, this crash might be desirable as a way to signal to the developer so they correct the situation. You might even actually use
assert to show a meaningful error.
On the other hand, this type of boilerplate code can be tedious and decrease legibility. In this very simple example we only have one dependency called once in our code, but the situation can get ugly if we have multiple dependencies that are called at different points.
Furthermore, if we have just a few dependencies, Constructor Injection can make our initializers quite long and cumbersome to write.
Readers of Robert C. Martin’s Clean Code might remember the very strong recommendation of keeping function argument lists short:
The ideal number of arguments for a function is zero (niladic). Next comes one (monadic), followed closely by two (dyadic). The arguments (triadic) should be avoided where possible. More than three requires very special justification-and then shouldn’t be used anyway.
But you might say Swift permits us to declare the dependency as an
Optional to avoid such a crashes, and you would be right.
When declared this way, the Injector code can use a different initializer than
HelloWorldViewController(helloWorldService:) ~ as it is often the case with
UIViewController~ and configure the dependency via a setter (Setter Injection). The dependency can be called even if not properly set and nothing bad will happen. Right?
The issue here is that in this approach the missing dependencies will go unnoticed. This might be appropriate in some cases, but most likely we will want to be sure that we consume all the dependencies that we have injected into a Client.
Furthermore, in most instances you will probably still use guard let or some other boilerplate code to unwrap your now
Optional dependency. Not ideal.
Verbosity and Over-engineering
As we saw before, checking for the state of dependencies can have the side effect of causing you to write some amount of boilerplate code.
Additionally you might have noticed that to create this very simple “Hello World” example we had to create a few extra protocols and classes that we wouldn’t have to write if we had taken the alternative approach.
In a real world Application with many features, the number of protocols and classes will likely be bigger. It is possible to over-engineer your dependency injection to a point where the code becomes highly fragmented, hard to read and difficult to update. You might end up with a large number of small little classes that do not much at all, all over the place.
Loss of IDE functionality
Last but not least, the fact that Dependency Injection has a strong focus on working with abstract functionality and not concrete implementation, makes it hard for Xcode to infer which specific implementation a developer might be referring to.
Sources / Further reading
- Wikipedia, Dependency Injection Retrieved from https://en.wikipedia.org/wiki/Dependency_injection#Disadvantages
- Wikipedia, Dependency Inversion Principle Retrieved from https://en.wikipedia.org/wiki/Dependency_inversion_principle
- Robert C. Martin, Clean Code, Prentice Hall; 1 edition (August 11, 2008)