In a previous post we had the opportunity to define the architecture of our app conceptually. The purpose of this post is to get deep into implementation of key components of this architecture we’re using.
In this post we’ll talk about how we implement:
I think that this quote by James Shore defines what dependency injection is:
Dependency injection means giving an object its instance variables. Really. That’s it.
Dependency injection is a software design pattern that helps you to make the code more testable reducing the coupling.
Habitually dependency injection pattern is confronted to singleton pattern. In my opinion, based on my experience as iOS developer, I recommend using dependency injection pattern in most of the cases, because, among other things, it’s handy for isolating classes during testing. Although both patterns are valid depending on each case, because certain components of the projects work better with certain access patterns.
There are some alternatives like Swinject to implement dependency injection in a project, but before adding a new dependency to a project, we like to make us a key question: is it really necessary? can we resolve this need without adding third-party dependencies? In this case we thought that yes, we could! We realized that we didn’t really needed any external library and here is why…
Let’s get into implementation with an example of a common case in our app:
You have a UIViewController with a Presenter. This Presenter needs an object to fill the view and has a use case that fetch data from a repository.
Our proposal is define a protocol with a list of methods which will receive all the instance variables needed to return an instance ready to be used. We name this protocol
Notice that we need to initialize the use case repository to inject the use case to the presenter.
Now we can use this protocol to instantiate the UIViewController:
The advantages of using these assemblers to resolve dependencies are:
- No third-party dependencies.
- The arguments needed to resolve a dependency are typed, so developer can’t make mistakes.
- To instantiate a component you need to resolve its dependencies, this guarantees its well behavior.
- Every component of the app is decoupled and can be testable injecting mocks.
- Helps you to avoid abusing shared instances. (… or at least makes you think about if they’re necessary)
Hope you found this post interesting and useful for your projects. Any question or comment will be welcome!
Thanks and good luck!!!
In the following posts we detail other key components from our architecture:
This implementation got inspired by this post.