要使用工具, 首先还是先来了解一下为什么要使用它? 而这里有一篇很好的文章说明为什么要进行依赖注入, 以及一些相关概念.
把前因后果看懂之后, 来尝试使用一些工具提高开发效率和质量...
这里使用的是 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")
// ...
}
}
基本使用就介绍到这里, 之后会写一些高级用法.