Holy Swift

Holy Swift

Dependency Injection using Property Wrappers in Swift

Dependency Injection using Property Wrappers in Swift

Such a big property!

Subscribe to my newsletter and never miss my upcoming articles

Hallo vrienden, Leo hier.

Dependency Injection is a very old programming topic. No wonder that we have so many resource about it in the web, and it can be used to help you accomplish the old saying: Compositions over inheritance. I'm not saying that inheritance is something inherently bad, it has it's place in the sun when used properly. I personally prefer compositions because we reduce coupling and it's easier to test each part.

There's many articles , many tutorials , many frameworks that explain how can you do dependency injection in Swift. With initialisers, closures, singletons, property wrappers, remote dependencies, static subscripts, extensions.... all the flavours you want. Today we will explore with property wrappers, and that's look like the android Dagger.

Disclaimer: This wasn't stress tested and it's just a starting point of a type of dependency injection technique. Be careful importing directly to your projects.

No more talking let's write it down, but first...

The Painting

This painting is a 1907 masterpiece from Alexandre Benois called Military Parade of Emperor Paul in front of Mikhailovsky Castle.

Alexandre Nikolayevich Benois , born 3 May 1870 in Saint Petersburg and died 9 February 1960 in Paris. Was a Russian artist, art critic, historian, preservationist, and founding member of Mir iskusstva (World of Art), an art movement and magazine.

Created with more detail than most of his works, Benois illustrates the still formality of the parade by filling the sky with lightly falling snow. Although the Emperor Paul is not within view, his presence is indicated by the lamppost and barricade in the foreground, pulling the viewer into the painting by indicating that they are within reach of their ruler.

Today we will talk about property wrappers, and this painting is literally showing how can a real property can be wrapped by the royalty and the military force.

The Problem

You want to have an easy way to inject dependencies into your classes and structs.

Let's start from the beginning. So in the end what we want to achieve? In the end we want to inject classes into our view model so this way we don't need to worry about that in view model initialiser. We should aim for this:

struct UserPhotoViewModel {

    @Inject<PhotoManager> private var photoManager: PhotoManager
    @Inject<AnalogicPhotoManager> private var analogicPhotoManager: AnalogicPhotoManager

    func makeItWork(){
        print(photoManager.getAllPhotos())
        print(analogicPhotoManager.getAllOldPhotos())
    }
}

In our example we want to create a UserPhotoViewModel that needs managers to work properly, the PhotosManager and the AnalogicPhotoManager. But how can we do that?

Diving in Dependency Injection

Let's start with the protocols:

protocol Component: AnyObject {
    init()
}

protocol PhotosManaging: Component {
    func getAllPhotos() -> [String]
}

protocol OldPhotosManaging: Component {
    func getAllOldPhotos() -> [Int]
}

These three protocols will serve as a solid type base for what we want to achieve. The PhotosManaging and OldPhotosManaging both conforms to the Component. The Component guarantee to us that any manager can be made without have to pass anything to it.

Now that we have the protocols let's create the managers.

final class PhotoManager: PhotosManaging {

    init() {}

    func getAllPhotos() -> [String] {
        return ["Photo A", "Photo B", "Photo C"]
    }
}

final class AnalogicPhotoManager: OldPhotosManaging {

    init() {}

    func getAllOldPhotos() -> [Int] {
        return [323, 2, 5555]
    }
}

Here I just put some stub output to show how this could be used. Nothing too complex.

The Resolver

Now we will do the Resolver class. The resolver class as it name says, it resolves types for you. So with the resolver you can do two things: add new injectables components and get those components. We will create it as a Singleton, but you can do it whatever you want.

class Resolver {
    static let shared = Resolver() // Mark 1
    private init() {} // Mark 1

    var factoryDict: [String: AnyObject] = [:] // Mark 2

    private func add<T: Component>(_ factory: T) { // Mark 3
        factoryDict[String(describing: T.self)] = factory
    }

    func resolve<T: Component>(_ type: T.Type) -> T { // Mark 4
        guard let cachedType = factoryDict[String(describing: type)] as? T else {
            let newObject = type.init()
            add(newObject)
            return newObject
        }

        return cachedType
    }
}

Let's break it down:

  1. The Mark 1 is the singleton setup, we create one static instance of Resolver and everyone can use from the shared property.
  2. On Mark 2 we create our cache of injectable things. It's important to be a dictionary so we can just get the object with the key that represents the type.
  3. Mark 3 is the first function called add. To add to the dictionary we use the type.self description as a key and the value is the object itself.
  4. The mark 4 is the other function and the only accessible outside the Resolver class, now we are trying to get the component we add in the function above mentioned. It will receive any type that conforms to Component so we can create or return them if they already exists.

The next thing is the cherry of the cake, the Property Wrapper:

@propertyWrapper
struct Inject<T: Component> {

    var component: T

    init() {
        self.component = Resolver.shared.resolve(T.self)
    }

    public var wrappedValue: T {
        get { return component }
    }
}

Now that we have a way to search and get any component we just need to wrap it into a property wrapper. The initialisation is the key here, we get the component from the resolver without have to explicit it anywhere here. It's all about the generics!

The structure is done! Now let's test our solution:

struct UserPhotoViewModel {

    @Inject<PhotoManager> private var photoManager: PhotoManager
    @Inject<AnalogicPhotoManager> private var analogicPhotoManager: AnalogicPhotoManager

    func makeItWork(){
        print(photoManager.getAllPhotos())
        print(analogicPhotoManager.getAllOldPhotos())
    }
}

let userPhotoViewModel = UserPhotoViewModel()

userPhotoViewModel.makeItWork()

The result should be:

Screenshot 2021-09-29 at 20.06.38.png

We are done!

Summary

Today we study one way to do dependency injection. Of course this way has the drawback that we can only inject things that have an empty initialiser, who knows in the future I can bring other types of dependency injection that you can inject anything.

That's all my people, I hope you liked as I enjoyed write this article. If you want to support this blog you can Buy Me a Coffee or just leave a comment saying hello. You can also sponsor posts! Just reach me in LinkedIn or Twitter for details.

Thanks for the reading and... That's all folks.

credits: image

Interested in reading more such articles from Leonardo Maia Pugliese?

Support the author by donating an amount of your choice.

 
Share this