使用 Swift Macros 进行无成本依赖注入

MDI

利用 Swift 宏实现零成本依赖注入

功能

  • 通过 Swift 宏实现零成本依赖注入
    • @AutoRegister 对所有工厂的依赖关系调用 resolve()
    • @SingletonRegister 遵循与 @AutoRegister 相同的原则,但将生成的值存储(并公开)在单例中
    • @FactoryRegisterresolve(...) 方法公开所有参数
  • 类型安全
  • 生成纯 Swift

要求

  • 需要 Swift 5.9

使用示例

定义一种类型,它既充当“程序集中心”,又充当解析入口点。 一个空 enum 应该足够了。

enum Dependency { }

在本示例中,程序集将在 Dependency 的扩展中定义(虽然这不是强制的,并且可以在类型声明上直接完成)。 在下面的示例中,我们将假定以下类型:

protocol ABTestingProtocol { }
protocol CodeGuardsProtocol { }
protocol ThemeProtocol { }

final class ABTesting: ABTestingProtocol { }
final class CodeGuards: CodeGuardsProtocol { }
final class Theme: ThemeProtocol {
    private let abTesting: ABTestingProtocol
    private let codeGuards: CodeGuardsProtocol

    init(
        abTesting: ABTestingProtocol,
        codeGuards: CodeGuardsProtocol
    ) {
        self.abTesting = abTesting
        self.codeGuards = codeGuards
    }
}

然后创建一个程序集来注册它们:

import MDI

@SingletonRegister((any ABTestingProtocol).self, factory: ABTesting.init)
@SingletonRegister((any CodeGuardsProtocol).self, factory: CodeGuards.init)
@SingletonRegister((any ThemeProtocol).self, factory: Theme.init(abTesting:codeGuards:))
extension Dependency { }

@SingletonRegister 将对 ABTestingProtocolCodeGuardsProtocol 调用 resolve。 由于两者都在程序集中声明(注意它们很容易在其他地方声明),因此这会成功;否则,我们将收到编译器错误。 请注意,在前面的示例中,所有依赖关系都是单例,但显然不必如此。

如果使用了 @AutoRegister

import MDI

@AutoRegister((any ABTestingProtocol).self, factory: ABTesting.init)
@AutoRegister((any CodeGuardsProtocol).self, factory: CodeGuards.init)
@AutoRegister((any ThemeProtocol).self, factory: Theme.init(abTesting:codeGuards:))
extension Dependency { }

在每次调用 resolve(...) 时都会创建注册类型的新的实例。

最后,某些依赖关系需要无法解析的参数,而是在实例化时传递。 这可以通过 @FactoryRegister 轻松实现。 在下面的示例中,我们可以解析 ThemeProtocol,但不一定是 boot: DatesessionId: String

protocol AppContextProtocol {}

final class AppContext: AppContextProtocol {
    let boot: Date
    let sessionId: String
    let theme: ThemeProtocol

    init(
        boot: Date,
        sessionId: String,
        theme: ThemeProtocol
    ) {
        self.boot = boot
        self.sessionId = sessionId
        self.theme = theme
    }
}

通过使用 @FactoryRegister,我们可以在创建工厂方法时公开所需的参数,甚至利用 resolve(...) 来解析 ThemeProtocol

import MDI

private extension AppContext {
    static func factory(
        boot: Date,
        sessionId: String
    ) -> AppContext {
        return AppContext(
            boot: boot,
            sessionId: sessionId,
            theme: Dependency.resolve()
        )
    }
}

@FactoryRegister(
    (any AppContextProtocol).self,
    parameterTypes: Date.self, String.self,
    factory: AppContext.factory(boot:sessionId:)
)
extension Dependency { }

使用此方法,Dependency 将公开类型为以下的 resolve 方法:

Dependency.resolve(_ arg1: Date, _ arg1: String) -> any AppContextProtocol

安装

Swift 包管理器

你可以通过将 Swift 包管理器添加到你的 Package.swift 文件作为依赖项来使用它来安装你的软件包:

dependencies: [
    .package(url: "Macro