Haibo Zhou's site

Mobile Development Articles

First introduction of Dependency Injection.

I haven't updated my blog recently as I got some interviews recently. And I need to prepare each one properly becuase I really wanna got an iOS related job.

Today I'd like talk about Dependency Injection. In recent, I have refactor one of my app with it and also I was asked if I used it before during one interview.

What is Dependency Injection?

 

Normally, we use it to manage global state of our app. For instance, we have a music app, and we want to manage those properties like songs, currIndex and playMode in a global poll, so each ViewController will use this unique data source to keep them synced with each other. Another thing is we usually use it to replace singletons in our project. Because the singleton pattern increases coupling whereas dependency injection reduces coupling.

Ok, enough talk for the theory. Let's see how to create a Dependency Injection.

 

An Example

In this example, I create a UIViewController subclass, it has a property stateManager with type of StateManager, which is our dependency class.

import UIKit

class ViewController: UIViewController {
    var stateManager: StateManager!
}

 

StateManager

import Foundation

enum PlayMode: String {
    case cycle = "repeat"
    case cycleOne = "repeat.1"
    case shuffle = "shuffle"
}

class StateManager {
    var songs: [Song]? = nil
        var position = 0
    var playingMode: PlayMode? = .cycle
}

 

inject the dependency

// Initialize View Controller
let vc = ViewController()

// inject the dependency(state manager)
viewController.stateManager = StateManager()

 

In this way, we create dependency object outside our ViewController and inject/assign it to stateManager of ViewController. That means ViewController class only knows StateManager class but don't know its initialization since we create that instance outside ViewController. That is very important!

Great, I think you have got what is Dependency Injection and how to use it. But it is just 1 form of it, called property injection, because we are actually passing a StateManager instance to ViewController's property.

 

Generally, there are three types of Dependency Injection:

  • initializer/constructor injection
  • property injection
  • method injection

  Well, they may be named slightly different from various developers, though it doesn't matter. I have covered property injection above, let's see the other two types.

 

Initializer Injection

Most of developers will prefer to use this type. I will explain per below example.

struct User {
    var id: UUID
    var name: String
}

struct UserStatusProvider {

    private let storage: UserLocalStorage

    init(storage: UserLocalStorage) {
        self.storage = storage
    }

    func isLoggedIn() -> Bool {
        // 1. create UserLocalStorage instance inside UserStatusProvider
                // return UserLocalStorage().getUser() != nil
                
                // 2. create UserLocalStorage instance outside UserStatusProvider
        return storage.getUser()
    }
}

struct UserLocalStorage {

    func getUser() -> User? {
        return UserDefaults.standard.object(forKey: "UserStorageKey") as? User
    }
}

// initialize UserLocalStorage
let localStorage = UserLocalStorage()

// initialize UserStatusProvider
let userStatusProvider = UserStatusProvider(storage: localStorage)

 

This is actually one of my interview question. Here we want to inject a dependency into UserStatusProvider object, instead of passing the property storage directly, we pass that dependency during initialization of UserStatusProvider. The major benifit is that dependency passed in during initialization can be made immutable.

The localStorage instance is passed as an argument during initialization. The init(storage: method is the designated initializer. Another benfit is the designated initializer clearly shows what the dependency of the UserStatusProvider struct are, because we are required to pass storage as an parameter during initialization. And that passed parameter in init() is immutable by default.

You may have a question? What about constructor injection in ViewController, why you change it to struct/class for the 2nd example? Well, that is because I found use constructor injection in ViewController is a bit tricky, we need to deal with ViewController's designated initializer. Let's see below example.

import UIKit

class ViewController: UIViewController {
    var stateManager: StateManager!
    
    // 1. I don't want to use designated initializer
    override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) {
        
    }
    
    // 2. or use convenience initializer
    convenience init(modelController: ModelController) {
        self.init()
        self.modelController = modelController
    }
}

 

Use designated initializer is sort of complicated, we need to call super.init() to provide the nib and bundle, though I don't use nib and bundle, just put them as nil. Then another function is required to pass in the modelController (dependency).

I found I could use convenience initializer instead of designated initializer as the code shown, but for now I only test it on a small project. That is things about best practice, so I want to check it completely on some complicated project later...

 

designated initializer vs convenience initializer? Check This Apple doc

I would not cover Method Injection in this article as I didn't try it yet. I don't want to give the wrong information to others.

Well, here is a Repo to test property injection, feel free to have a look.

 

It includes:

  • UITabBarController, NavigationViewController, those are popular used when creating an app.
  • Where to create the entry point for dependency injection in real project? Check the SceneDelegate file.
  • How to pass Dependency to UITabBar SubViewController? Check TabBarController file, you could found it.
  • How to use convenience init on ViewController? Check NewViewController file.

 

Okay, that is everything for this session, just a simple taste of Dependency Injection. I didn't cover topics like Unit Test or Singletons vs Dependency Injection. Maybe add them later~

Tagged with: