Dependency Injection in Swift

Create easy modules, test smiling.

If you are reading this, I’ll think that you want to learn first whats is Dependency Injection. I think that a little of code can explain more than words.

class ProfileViewController: UIViewController {

    var userName: String!
    @IBOutlet var labelName: UILabel!

    override func viewDidLoad() {
        super.viewDidLoad()
        labelName.text = userName
    }

}

This view needs userName to work, it only need to be a String. Now think that userName is a result of a request result from backend. But in your Unit Tests you will not call the backend, but you need that ProfileViewController works. You will Inject manually a userName, because the view Depends on the userName.

Is it? I understand all about DI in Swift?” – NO!

Protocols

To understand better how test your app easily and work with modules you need understand about Protocols.

Protocols are contracts! As a contract that say to both interested whats each part need to do, Protocols to be a generic Interface that permit you implement with different behaviours.

The Problem:

You have a User Profile view, that only show the name and you can update it.
Let’s Code:

protocol UserPresenterProtocol {
    func getName() -> String
    func updateName(name: String)
}

This protocol create a contract that it will give the name and update it too.

BUT, the protocol don’t do anything. You need implement it.

class UserPresenter: UserPresenterProtocol {
    var savedName = "Narlei Moreira"
    func getName() -> String {
        return savedName
    }

    func updateName(name: String) {
        savedName = name
    }
}

Here I’m saving the name in a var, but it can be a backend request or a database request.

class ProfileViewController: UIViewController {
    var presenter: UserPresenterProtocol?
    @IBOutlet var labelName: UILabel!

    override func viewDidLoad() {
        super.viewDidLoad()
        labelName.text = presenter?.getName()
    }
}

The view controllers needs a UserPresenterProtocol to get name. You need inject in the view initializer. Normally dependencies need to be force unwrapped, but in some cases, because memory management, need to be optionals to prevent memory leaks. Each case is a case, because force unwrap in iOS causes crashes, sometimes is better show to the user a message and close the view.

Let’s see other examples:

protocol APIProtocol {
    func get(url: URL) -> [String: Any]
    func post(url: URL, parameters: [String: Any])
}

class UserInteractor {
    
    var api: APIProtocol
    
    init(api: APIProtocol) {
        self.api = api
    }
    
    func saveUser(name: String) {
        api.post(url: URL(string: "https://XYZ.com/user")!, parameters: ["name" : name])
    }
    
    func getUser() -> String {
        let result = api.get(url: URL(string: "https://XYZ.com/user/me")!)
        return result["name"] as! String
    }
}

Here, you see that if I implement the APIProtocol making URL request the app will show the backend results, but to test you only need to create a mock API implementation. 

If you use database when is offline and REST api when is online, you only need create two implementations and inject in different behaviours. 

And you need use Swift default value:

class APIWeb: APIProtocol {
    ...
}

class UserInteractor {
    
    var api: APIProtocol
    
    init(api: APIProtocol = APIWeb()) {
        self.api = api
    }
    ...
}

Now talking about libs to help your work…

There are some libraries that can turn your DI implementation more easy like: Swinject, but I create one too that help you to start without need refactor all your code.

It’s Dependency Manager Inject – DMInject

How it works?

First Step – add mapping:

 

import DMInject

class DMInjectionMapper: DMInjectionMapperProtocol {
    var arrayReturn = [Any]()

    // Use this map to add the objects, the DMInject will get by type
    func initialize() {
        arrayReturn.append(UserPresenter())
    }

    func getAllInjections() -> [Any] {
        return arrayReturn
    }
}

Add to your AppDelegate:

DMInject.main.initialize(mapper: DMInjectionMapper())

Now is only use:

// Exemple 1: Easy way
var myclass: MyClassProtocol?
myclass << DMInject()
if let name = myclass?.getName() {
    print(name)
}


// Exemple 2: Declarative way
let myClass2 = DMInject.main.getInstance(interface: MyClassProtocol.self)
if let name = myClass2?.getName() {
    print(name)
}

// Example 3: Using Property Wraper
@Inject
var myClassX: MyClassProtocol?

In this lib, I’m working with optionals because as I say, sometimes, is better show a error and abort the view than crash the app, but if you need to use force unwrap, you can use: DMInject.main.getInstance(interface: MyClassProtocol.self)!

That’s it, I hope you understand the principles.

I hope this helps you, if you have questions of suggestions, comment here or call me in my social networks.

2+