Resolve your dependencies with Resolver

Introduction

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 initialized 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 the handle the initialisation of objects and the initialisation of their 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). This scoping is usually done within "containers". Usually you have one main container and add different nested containers to that container to scope the different dependencies.

Whether you use a dependency injection framework or use your own strategy, using dependency injection brings 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 application.

import Resolver

extension Resolver: ResolverRegistering { 
    pubic static func registerAllServices() {
        register {
            Dependency() as DependencyProtocol // Register dependency here
        }
    }
}

In thise file you can register all your dependencies.

When programming in Swift I like the protocol orienteid approach, so you can register dependencies as shown above.

This file functions as your main container.

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

import Resolver

extension Resolver {
    static func registerPartOfYourAppDependencies() {
        partOfYourApp.register {
            ServiceToRegister() as ServiceToRegisterProtocol
        }
    }
}

Then you need to add this container to your main container. Like so:

import Resolver

extension Resolver: ResolverRegistering { 
    static let partOfYourApp = Resolver(parent: main) // set a static property that you can call from your code
    
    pubic static func registerAllServices() {
        register {
            Dependency() as DependencyProtocol // Register here
        }
        
        registerPartOfYourAppDependencies() // register all services from the separate container
    }
}

To ensure that either all types use the same instance of a type or always a new one, Resolver provides so called scopes.

import Resolver

extension Resolver: ResolverRegistering { 
    pubic static func registerAllServices() {
        Resolver.defaultScope = ResolverScopeUnique() // Set a default scope for all your dependencies
        
        register {
            Dependency() as DependencyProtocol // Register dependency here
        }
        
        register {
            Dependency2() as Dependency2Protocol // Register dependency here
        }.scope(.application)
    }
}

In the example above we define we want to always use the same instance of AlwaysTheSameInstance by defining the scope applicationand that we want to use a new instance for AlwaysANewInstance by defining the scope unique.

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.

import Resolver

class Object {
    private let fromMainContainer: DependencyProtocol
    private let fromSubContainer: ServiceToRegisterProtocol
    
    init(
        fromMainContainer: DepedencyProtocol = Resolver.resolve(),
        fromSubContainer: ServiceToRegisterProtocol = Resolver.partOfYourApp.resolve()
    ) {
        self.fromMainContainer = fromMainContainer
        self.fromSubContainer = fromSubContainer
    }
}

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 Resolver.partOfYourApp.resolve().

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:

import Resolver

class Object {
    @Injected var fromMainContainer: DependencyProtocol
    @Injected(container: Resolver.partOfYourApp) var fromSubContainer: ServiceToRegisterProtocol
    
    init() {
        // Nothing to do here
    }
}

Summary

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.