swift-blade

Swift-blade 是一个驱动的 Swift 依赖注入框架。

它深受 Dagger 的启发。

参考

API 文档

安装

Swift 包管理器

在你的 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
    }
}

声明 Provider

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 可以获取 ElectricHeaterThermosiphonCoffeeMaker 类型的实例。但是,它还不知道如何满足 HeaterPump 类型的依赖项。

由于 HeaterPump 是不能直接初始化的协议类型,我们将定义静态 @Provider 函数来声明如何满足这些类型的依赖项。

静态 @Provider 函数可以有自己的依赖项。由于 swift-blade 知道如何获取 ElectricHeaterThermosiphon 类型的实例,因此 HeaterPump 的 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 宏无法知道初始化器所属的类型,否则就无法知道。