设计模式

iOS 依赖注入工具 Swinject

2017-10-10  本文已影响780人  貘鸣

要使用工具, 首先还是先来了解一下为什么要使用它? 而这里有一篇很好的文章说明为什么要进行依赖注入, 以及一些相关概念.

把前因后果看懂之后, 来尝试使用一些工具提高开发效率和质量...

这里使用的是 Swinject, 它的 github 地址在这里.

下面的例子是根据这篇文章来写的.

类之间强耦合的情况

首先来看如果没有依赖注入的时候的情况, 一般编程时都会容易将两个类直接耦合在一起, 并且依赖的是实现, 而不是抽象. 而这也的做法在实践中是不可取的:

Cat 类:

class Cat {
    let name: String

    init(name: String) {
        self.name = name
    }

    func sound() -> String {
        return "Meow!"
    }
}

PetOwner 类:

class PetOwner {
    let pet = Cat(name: "Mimi")

    func play() -> String {
        return "I'm playing with \(pet.name). \(pet.sound())"
    }
}

实际使用的时候:

let petOwner = PetOwner()
print(petOwner.play()) // 输出 "I'm playing with Mimi. Meow!"

乍一看貌似没问题, 但比如之后需要扩展的时候呢? 宠物主人不一定都是喜欢猫的, 那如果喜欢的是狗呢? 难道还要再新建一个 DogOwner?

所以这里就必须要对两个类的依赖进行解耦, 并且改变为依赖抽象(比如依赖于接口), 这样之后再进行依赖替换的时候就很容易了.

对依赖进行解耦

下面就来改写上面的代码.

首先将宠物抽象为一个接口(协议), 这样做的价值在于能够将依赖解耦, 使用者的代码依赖于这个接口, 而非具体的实现, 因而在替换依赖时就很方便了.

protocol AnimalType {
    var name: String { get }
    func sound() -> String
}

让 Cat 类实现这个协议:

class Cat: AnimalType {
    let name: String

    init(name: String) {
        self.name = name
    }

    func sound() -> String {
        return "Meow!"
    }
}

将 PetOwner 类修改为依赖于 AnimalType 接口, 而非 Cat 实现, 并且使用构造注入(构造注入的概念详见开篇给出的链接):

class PetOwner {
    let pet: AnimalType

    init(pet: AnimalType) {
        self.pet = pet
    }

    func play() -> String {
        return "I'm playing with \(pet.name). \(pet.sound())"
    }
}

则使用的时候:

let catOwner = PetOwner(pet: Cat(name: "Mimi"))
print(catOwner.play()) // 输出 "I'm playing with Mimi. Meow!"

如果是要给宠物主人一条狗来溜溜:

class Dog: AnimalType {
    let name: String

    init(name: String) {
        self.name = name
    }

    func sound() -> String {
        return "Bow wow!"
    }
}

则使用的时候:

let dogOwner = PetOwner(pet: Dog(name: "Hachi"))
print(dogOwner.play()) // 输出 "I'm playing with Hachi. Bow wow!"

不过上述代码也仅是手动对依赖进行注入, 但如果是应用中的依赖关系过多且复杂, 则手动管理一样是件头疼的事情, 这时就可以利用一些工具了, 比如说 Swinject.

使用 Swinject 进行依赖注入

这里仅介绍它的简单使用, 详细请参考 Swinject 的 github 主页.

首先引入它:

import Swinject

然后配置依赖管理容器(一般在 appdelegate 中配置):

    func configContainer() {
        let container = Container()
        container.register(IAnimal.self) { _ in Cat(name: "Mimi") }
        container.register(PetOwner.self) { resolver in
            PetOwner(pet: resolver.resolve(IAnimal.self)!)
        }
    }

上面的代码中, 首先告诉依赖管理容器, 将 IAnimal 注入为 Cat 对象. 然后继续指定 PetOwner 是一个由容器管理的对象, 且 PetOwner 的依赖是由容器提供.

这样在程序中使用到 PetOwner 时, 可以这样写:

let petOwner = container.resolve(PetOwner.self)!
print(petOwner.play()) // prints "I'm playing with Mimi. Meow!"

这里就相当于每次都在 container 中去手动获取想要的对象. 而对象的依赖是事先配置好的, 创建对象的时候就会进行依赖注入.

更简单的做法是将容器统一在某个类中包装起来:

class DIContainer {
    static let container: Container = {
        let ctner = Container()
        ctner.register(IAnimal.self) { _ in Cat(name: "Mimi") }
        ctner.register(PetOwner.self) { resolver in
            PetOwner(pet: resolver.resolve(IAnimal.self) ?? Cat(name: "none"))
        }
        return ctner
    }()
}

由于 swift 中的类属性都是 lazy 方式求值的, 所以需要在 AppDelegate 中先引用一次, 以保证 container 中的所有注册内容都是在程序最开始就运行:

class AppDelegate: UIResponder, UIApplicationDelegate {

    // ...
    let container = DIContainer.container
    // ...
}

这种方式下使用就更方便了:

class ViewController: UIViewController {
    // ...
    let container = DIContainer.container
    // ...
    override func viewDidLoad() {
        super.viewDidLoad()
        let petOwner = container.resolve(PetOwner.self)
        print(petOwner?.play() ?? "none")
        // ...
    }
}

基本使用就介绍到这里, 之后会写一些高级用法.

上一篇 下一篇

猜你喜欢

热点阅读