Swift-blade 是一个宏驱动的 Swift 依赖注入框架。
它深受 Dagger 的启发。
在你的 Package.swift
文件中声明 swift-blade 为依赖项
.package(url: "https://github.com/shackley/swift-blade", from: "0.1.5")
将 Blade 作为依赖项添加到你的 target(s) 中
dependencies: [
.product(name: "Blade", package: "swift-blade")
]
我们将通过构建一个简单的咖啡机来演示 swift-blade 的依赖注入。有关您可以编译和运行的完整示例代码,请参阅 swift-blade-example。
我们将从为我们的咖啡机定义一些类型开始。
Swift-blade 利用初始化器进行依赖注入,因此我们将像不使用依赖注入框架一样编写我们的类。
protocol Heater {}
protocol Pump {}
class ElectricHeater: Heater {
init() {}
}
class Thermosiphon: Pump {
private let heater: Heater
init(heater: Heater) {
self.heater = heater
}
}
class CoffeeMaker {
private let heater: Heater
private let pump: Pump
init(heater: Heater, pump: Pump) {
self.heater = heater
self.pump = pump
}
}
Swift-blade 负责初始化应用程序类的实例并提供它们的依赖项。
为了做到这一点,必须告诉 swift-blade 如何获取它遇到的每种类型的实例。这就是 @Provider
属性的用武之地。
让我们回到前面,在我们的类的初始化器中添加 @Provider
属性,以便 swift-blade 知道如何获取它们的实例。”
注意
基于初始化器的 @Provider
必须通过 @Provider
属性的 of
参数指定其返回类型。
class ElectricHeater: Heater {
@Provider(of: ElectricHeater.self)
init() {}
}
class Thermosiphon: Pump {
private let heater: Heater
@Provider(of: Thermosiphon.self)
init(heater: Heater) {
self.heater = heater
}
}
class CoffeeMaker {
private let heater: Heater
private let pump: Pump
@Provider(of: CoffeeMaker.self)
init(heater: Heater, pump: Pump) {
self.heater = heater
self.pump = pump
}
}
现在 swift-blade 可以获取 ElectricHeater
、Thermosiphon
和 CoffeeMaker
类型的实例。但是,它还不知道如何满足 Heater
和 Pump
类型的依赖项。
由于 Heater
和 Pump
是不能直接初始化的协议类型,我们将定义静态 @Provider
函数来声明如何满足这些类型的依赖项。
静态 @Provider
函数可以有自己的依赖项。由于 swift-blade 知道如何获取 ElectricHeater
和 Thermosiphon
类型的实例,因此 Heater
和 Pump
的 provider 可以写成
@Provider
static func provideHeater(heater: ElectricHeater) -> Heater {
heater
}
@Provider
static func providePump(pump: Thermosiphon) -> Pump {
pump
}
这种模式通常用于将具体类型别名为它所符合的协议。
所有 provider 必须注册到一个模块。模块只是具有 @Module
属性的空枚举。
基于初始化器的 @Provider
通过 @Module
属性的 provides
参数指定提供的类型来包含在模块中。
静态 @Provider
直接嵌入在模块中。
@Module(provides: [ElectricHeater.self, Thermosiphon.self, CoffeeMaker.self])
public enum CoffeeModule {
@Provider
static func provideHeater(heater: ElectricHeater) -> Heater {
heater
}
@Provider
static func providePump(pump: Thermosiphon) -> Pump {
pump
}
}
provider 函数形成一个类型图,通过它们的依赖项链接起来。
graph LR;
CoffeeMaker-->Heater;
CoffeeMaker-->Pump;
Pump-->Thermosiphon;
Thermosiphon-->Heater;
Heater-->ElectricHeater;
为了构建和访问图,我们首先需要定义它的根。该集合是通过一个协议定义的,该协议的函数没有参数并返回根类型。
通过将 @Component
属性应用于协议并声明可用于提供依赖项的模块,swift-blade 然后完全生成协议的实现。
@Component(modules = [CoffeeModule.self])
protocol CoffeeShop {
func maker() -> CoffeeMaker
}
该实现与协议同名,前缀为 "Blade"。现在,我们的 CoffeeApp
可以简单地使用生成的 CoffeeShop
实现来获取完全依赖注入的 CoffeeMaker
!
@main
struct CoffeeApp {
static func main() {
let coffeeShop = BladeCoffeeShop()
let coffeeMaker = coffeeShop.maker()
}
}
有关高级用法,请参阅 API 文档。
问:依赖图是如何验证的?
与 Dagger 不同,Blade 组件的依赖图在组件初始化后立即在运行时验证。如果依赖项没有注册的 provider,则会发生 fatalError
。这主要是因为当前的宏实现没有上下文 API,允许宏在扩展时收集关于声明中找到的类型的语义信息。如果添加了这样的 API,这种验证可能会在编译时发生。
问:为什么附加到初始化器的 @Provider
必须指定它们提供的类型?
目前,swift 宏在扩展时没有提供任何词法作用域信息,因此 @Provider
宏无法知道初始化器所属的类型,否则就无法知道。