Leonardo Maia Pugliese
Holy Swift

Holy Swift

Dependency Injection using Property Wrappers in Swift

Dependency Injection using Property Wrappers in Swift

Such a big property!

Leonardo Maia Pugliese's photo
Leonardo Maia Pugliese

Published on Sep 29, 2021

5 min read

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<PhotosManaging> private var photoManager: PhotosManaging
    @Inject<OldPhotosManaging> private var analogicPhotoManager: OldPhotosManaging

    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 {
    private var dependencies = [String: AnyObject]()
    private static var shared = Resolver()

    static func register<T>(_ dependency: T) {
        shared.register(dependency)
    }

    static func resolve<T>() -> T {
        shared.resolve()
    }

    private func register<T>(_ dependency: T) {
        let key = String(describing: T.self)
        dependencies[key] = dependency as AnyObject
    }

    private func resolve<T>() -> T {
        let key = String(describing: T.self)
        let dependency = dependencies[key] as? T

        assert(dependency != nil, "No dependency found")

        return dependency!
    }
}

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

@propertyWrapper
struct Inject<T> {
    var wrappedValue: T

    init() {
        self.wrappedValue = Resolver.resolve()
    }
}

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:

Resolver.register(PhotoManager() as PhotosManaging)
Resolver.register(AnalogicPhotoManager() as OldPhotosManaging)

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. Thanks for the user Blackjacx for pointing out my mistake here!

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

Did you find this article valuable?

Support Leonardo Maia Pugliese by becoming a sponsor. Any amount is appreciated!

Learn more about Hashnode Sponsors
 
Share this