When I started the wonderful journey of being an iOS Developer, I remember feeling really lost when everyone around me started talking about Dependency Injection. At that time, for me, it sounded like some unreachable, indomitable and impossible idea to grasp and not to mention, use it in my code. But, I also remember the day when my eyes were opened and I began to contemplate the power this design pattern gives to your code.
I really hope that when you finish reading this blog, you can feel encouraged to try out this design pattern (DI) in your code, and thus enjoy its advantages and power.
If you cringe when hearing the words dependency injection, or if you think that it’s a difficult pattern and it’s not meant for beginners, my friend, this blog is for you. The truth is that dependency injection is a fundamental pattern that is very easy to adopt.
So, from the top…
What is dependency injection?
Dependency injection is nothing more than injecting dependencies into an object instead of tasking the object with the responsibility of creating its dependencies. Is a great technique for decoupling code and making it easier to test.
The core idea is that rather than having our objects create their own dependencies by themselves, we inject them from the outside, enabling us to set them up differently depending on the situations — such as using mocks and other specific instances within our tests.
Let’s see a quick simple example of both cases:
Dependency Injection in action
We can inject a
GenericService instance into the
ViewModel instance. Even though the end result may appear identical, it definitely isn’t. By injecting the service, the view model doesn’t know how to instantiate the generic service.
No Dependency Injection
This option tasks the
ViewModel class with the instantiation of the
GenericService instance. As you can see the view model is in charge of creating the
This means that the
ViewModel class not only knows about the behavior of the
GenericService class, it also knows about its instantiation. That’s a subtle but important detail.
Ways of Injection:
The three most common forms to inject dependencies are the following:
I will briefly explain the two forms I usually use.
The idea is that an object should be given its dependencies on initialization. The big benefit of this method is that it guarantees that our objects have everything they need in order to do their work right away.
Dependencies can also be injected by declaring an internal or public property on the class or structure to hold the dependency. This may seem convenient, but it adds a loophole in that the dependency can be modified or replaced. In other words, the dependency isn’t immutable.
Many developers immediately discard this option because it’s cumbersome and unnecessarily complex. But if you consider the benefits, dependency injection becomes more appealing.
Let’s imagine we want to build a really cool app about SpaceX. One of the main screens will be in charge of showing all the past, current, and upcoming launches. Applying MVVM architecture on the app, assume we already have created the ViewLayer and decide to define the ViewModel this way:
Using this approach, when
LaunchesViewModelis instantiated, an
APIRequester object will be created too. With this approach, everything will run smoothly when the user decides to request any type of launch (past, current, or future): the
APIRequester will go to the API to request this information and it will be drawn in the view.
Houston, we have a problem!
But, what will happen if for any reason the information of the launches will no longer come from the API only, but can come from a database as well? This approach has coupled our ViewModel to fetch data from the API alone.
Let’s go a little further. Imagine we wanted to perform unit tests on the
LaunchesViewModel, we find ourselves tied up to the fact that the requester should always hit the API, and we all know that testing with production data can be really dangerous ⚠️.
This is when Dependency Injection comes to the rescue 🎉!
We are going to untangle this code in two steps:
As a first step, by making use of protocols Interfaces, we will add a layer of abstraction so that all those classes that implement this protocol will be a candidate to be injected as a dependency in the ViewModel.
As a second step, we will use one of the injection methods previously shown: the Initializer Injection; and define the requester dependency on
By applying a little Dependency Injection magic, the consumer
LaunchesViewModel is not rigidly coupled to just one type of Requester anymore 🤯!
We can inject now a dependency that uses an API for fetching data about launches:
Or if necessary, we can inject a dependency that goes to a Database for retrieving the data about all launches:
Now the consumer
LaunchesViewModel does not care about how to instantiate their dependency, not to mention where it comes from. And the best part is that the core functionality of the consumer is not affected at all!
- Separation of Concerns.
As you may have noticed, this pattern is very simple and has one prominent principle: separation of concerns. We remove responsibilities from the consumer of the dependency: what concrete implementation to use, how to configure it, and how to manage its lifetime. This lets us easily substitute dependencies in different contexts or in tests. All without changing its consumers. That makes consumers decoupled with their dependencies, making them easier to reuse, extend, develop, and test.
When we are testing our objects, we can easily set up unit tests and replace our dependency for another property of its type. This allows us to create mock objects and isolate the behavior in a way that allows us to understand the behavior of our code. With dependency injection, our class, or struct, becomes much more flexible in different contexts and does not have to change any of its internal code.
I hope that this blog has been useful to you, and made you feel encouraged to use this pattern in your code. We explored only the tip of the iceberg of this wonderful tool to decouple the code!