Resolve your dependencies with Resolver
Talking about dependency injection usually leads to a long discussion about different approaches and if you really need it. In every project.
But if there are also developers taking part in the discussion that do not just do iOS development they might get a bit confused that the discussion just has come up or they ask why you have not used it all along.
First I want to clearify that one always uses dependency injection when doing object oriented programming. You cannot get around it. Because dependency injection in its rawest form simply means that you hand an object the dependencies it needs. Meaning, every time when you have an object with properties and an initializer and this initializer has some arguments you are doing dependency injection (constructor injection).
The discussion usually comes up because it is easier for everybody working on the project if you use a consistent way of providing your objects with their needed dependencies.
In most larger projects you will have objects of which you want to use the same instance in multiple other objects or you want use something like a stateless service that is initialised every time it is used as an initializer argument.
When doing so it is easy to end up with many objects that all use the same instances of multiple objects which have other objects as their dependencies and so on. This then leads to having to carry all of those objects around and handing them from object to object, even though, you might not use all those objects at all places.
To keep sanity when handling dependencies and to keep the code base easier to maintain in regards to handling dependencies, many people use dependency injection frameworks. Frameworks that handle the initialisation of objects and the initialisation of the dependencies for you. Usually, they also provide you with a scoping meachnism. Meaning, that you can clearly state in which part of your app which objects need to be available (initialised). In addition to that you can usually have containers to separate dependencies based on the part of the app they are used in.
Whether you use a dependency injection framework or use your own strategy, using consistent dependency injection brings in general several advantages:
- Decoupling of repsonsibilities & Reusability of code is increased
- Refactoring is easier
- Testing is easier (easier to replace dependencies with mock objects)
Today I want you to give you a short introduction to a dependency injection framework that I have been using for the last couple of months. Resolver.
It is a very light weight framework that is written in Swift and very well maintained. It is still quite new and easy to integrate into your project. If you do not want to refactor your whole code base at once, you can also start adding it incrementally which is very convenient.
Add Resolver to your project
Adding Resolver to your project is fairly simple.
According to the their GitHub page you can add Resolver with CocoaPods, Carthage or SwiftPackageManager.
What dependency injection in general makes easier and what Resolver makes it a no brainer is controlling that types either use the same instances of other types or new ones.
Sometimes it is necessary to use the same instance of e.g. some kind of service through out your app because a lot of other types need to access properties/ methods of that type and then you need to ensure that every type actually uses the sames instance of that type. Other times you want to ensure that all types use a different instance of some other type. In that case you need to make that sure as well.
To organise your dependencies Resolver makes it possible to add different containers. Those containers hold different dependencies for different parts of your app. It makes sense to have one general container. Let’s call it
In this file you can register all your dependencies.
When programming in Swift I like the protocol oriented approach, so you can register dependencies as shown above.
This file functions as your
You can add all dependencies in this one file. But you can also split them app and add them to different containers (or you could also call it a name space) to group them more together. This can help you make clearer in which part of your app dependencies are/ should be used.
If you want to add a new container you would simply add a new file
Then you need to add this container to your
main container. Like so:
To ensure that either all types use the same instance of a type or always a new one, Resolver provides so called
In the example above we define we want to always use the same instance of
Dependency2 by defining the scope
applicationand that we want to use a new instance for
Dependency by defining the scope
Resolver provides even more scopes to give you full control of how your dependencies are injected.
In general there are many different strategies you can use to inject dependencies into your types. In this article I will show you two different strategies that I used when I used Resolver in a project.
Initializer based injection
Initializer based depdency injection is mainly a big name for something we have all done before. It is simple adding some arguments to the initializer of your type and ensuring that the type gets all the dependencies it has that way.
As you can see, we simply need to
import Resolver and can resolve the dependency by simple calling
Resolver.resolve() or when we want to resolve a dependency we have registered not in the
main container we call
Since you have defined the type of your argument, Resolver knows which type to resolve from the container.
Property based injection
Another way to resolve dependencies is to use the handy
property wrapper Resolver provides. That way, when you don't need to define initializer arugments but rather resolve your properties like so:
As you can tell from the very simple examples above you can easily add Resolver to your project and when you already use some of the two injection strategies shown above Resolver even makes it very easy to add it incrementally without having to change the whole code base all at once. As I said above, Resolver is very light weight and easy to add and easy to get rid of again as well if you and your team decides against it.