SafeDI

CI Status codecov License

Swift 项目的编译时安全依赖注入。SafeDI 为开发者提供手动依赖注入的安全性和简洁性,而无需编写大量的样板代码。

特性

核心概念

SafeDI 读取您的代码,验证您的依赖项,并在项目编译期间生成一个依赖树。如果您的代码能够编译通过,那么您的依赖树就是有效的。

将类型加入 SafeDI 依赖树非常简单:将 @Instantiable 宏添加到您的类型声明中,并使用宏装饰您的类型的依赖项以指示每个属性的生命周期。以下是 CoffeeMaker 中的 Boiler 在 SafeDI 中的样子

// The boiler type is opted into SafeDI because it has been decorated with the `@Instantiable` macro.
@Instantiable
public final class Boiler {
    public init(pump: Pump, waterReservoir: WaterReservoir) {
        self.pump = pump
        self.waterReservoir = waterReservoir
    }

    …

    // The boiler creates, or in SafeDI parlance ‘instantiates’, its pump.
    @Instantiated private let pump: Pump
    // The boiler receives a reference to a water reservoir that has been instantiated by the coffee maker.
    @Received private let waterReservoir: WaterReservoir
}

这就是全部!SafeDI 利用您现有类型上的宏装饰来定义您的依赖树。有关 SafeDI 宏及其用法的全面说明,请阅读我们手册的宏章节

开始使用

SafeDI 同时使用 Swift 宏和一个代码生成插件来读取您的代码并生成依赖树。要集成 SafeDI,请按照以下三个步骤操作

  1. 将 SafeDI 添加为项目的依赖项
  2. 将 SafeDI 的代码生成集成到您的构建中
  3. 使用 SafeDI 的宏创建您的依赖树

您可以在Examples 文件夹中查看示例集成。如果您要将现有项目迁移到 SafeDI,请按照我们的迁移指南进行操作。

添加 SafeDI 作为依赖项

Swift 包管理器

要将 SafeDI 框架作为依赖项添加到使用Swift Package Manager的包中,请将以下行添加到您的 Package.swift 文件中

dependencies: [
    .package(url: "https://github.com/dfed/SafeDI.git", from: "1.0.0"),
]

要使用 Swift Package Manager 将 SafeDI 框架安装到 Xcode 项目中,请按照Apple 的说明添加 https://github.com/dfed/SafeDI.git 作为依赖项。

CocoaPods

要将 SafeDI 框架作为依赖项添加到使用CocoaPods的包中,请将以下内容添加到您的 Podfile

pod 'SafeDI', '~> 1.0.0'

生成您的依赖树

SafeDI 提供了一个名为 SafeDIGenerator 的代码生成插件。此插件可以在有限数量的项目配置上直接使用。如果您的项目不属于这些受支持的配置,您可以配置您的构建以直接使用 SafeDITool 命令行可执行文件。

Swift 包管理器

Xcode 项目

如果您的第一方代码包含在 .xcodeproj 中的单个模块中,一旦您的 Xcode 项目依赖于 SafeDI 包,您可以通过转到目标的 Build Phases,展开 Run Build Tool Plug-ins 下拉菜单,并将 SafeDIGenerator 添加为构建工具插件来简单地集成 Swift Package Plugin。您可以在ExampleProjectIntegration 项目中看到这种集成实践。

如果您的 Xcode 项目包含多个模块,请按照上述步骤操作,然后创建一个 .safedi/configuration/include.csv 文件,其中包含一个逗号分隔的列表,列出根模块之外的 SafeDI 将扫描 Swift 源文件的文件夹。.safedi/ 文件夹必须放置在与您的 *.xcodeproj 相同的文件夹中,并且路径必须相对于同一文件夹。您可以在ExampleMultiProjectIntegration 项目中的此自定义的示例中看到。为了确保生成的 SafeDI 代码包含对所有必需模块的导入,您可以创建一个 .safedi/configuration/additionalImportedModules.csv,其中包含一个逗号分隔的模块名称列表以进行导入。

Swift 包

如果您的第一方代码完全包含在一个或多个模块的 Swift 包中,您可以将以下行添加到根目标的定义中

    plugins: [
        .plugin(name: "SafeDIGenerator", package: "SafeDI")
    ]

您可以在ExamplePackageIntegration 包中看到这种集成实践。

SafeDIGenerator Xcode 项目插件不同,SafeDIGenerator Swift 包插件可以在无需额外配置步骤的情况下查找依赖模块中的源文件。如果您发现 SafeDI 生成的依赖树缺少所需的导入,您可以创建一个 .safedi/configuration/additionalImportedModules.csv,其中包含一个逗号分隔的模块名称列表以进行导入。.safedi/ 文件夹必须放置在与您的 Package.swift 文件相同的文件夹中。

CocoaPods

使用预构建脚本下载 SafeDITool 二进制文件并生成您的 SafeDI 依赖树(示例)。请确保在运行预构建脚本的目标中将 ENABLE_USER_SCRIPT_SANDBOXING 设置为 NO

您可以在ExampleCocoaPodsIntegration 包中看到这种集成实践。运行 bundle exec pod install --project-directory=Examples/ExampleCocoaPodsIntegration 以创建 ExampleCocoaPodsIntegration.xcworkspace

其他配置

SafeDITool 旨在集成到任何大小或形状的项目中。如果您的第一方代码包含 Xcode 项目和 Swift 包的混合或其他配置,一旦您的 Xcode 项目依赖于 SafeDI 包,您将需要在类似于上面链接的 CocoaPods 集成的预构建脚本中直接使用 SafeDITool 命令行可执行文件。

SafeDITool 可以一次解析您的所有 Swift 文件,或者为了获得更好的性能,可以在构建过程中在每个依赖模块上运行该工具。运行 swift run SafeDITool --help 以查看该工具支持的参数的文档。

SafeDI 与其他 DI 库的比较

SafeDI 的编译时安全性和分层依赖作用域使其类似于 NeedleWeaver。与 Needle 不同,SafeDI 不需要为可以在 DI 树中实例化的每种类型定义依赖协议。与 Weaver 不同,SafeDI 不需要定义和维护与您的常规 Swift 代码并存的容器。

其他 Swift DI 库,例如 Swinjectswift-dependencies,不提供编译时安全性。与此同时,像 Factory 这样的库确实提供了依赖树的编译时验证,但阻止了分层依赖作用域。这意味着在使用 Factory 时,作用域依赖项(例如网络层中的身份验证令牌)只能有选择地注入。

贡献

很高兴您对 SafeDI 感兴趣,我很乐意看到您将其发展成什么样子。在提交 Pull Request 之前,请查看贡献指南

感谢您参与这个旅程,祝您注入愉快!

作者

SafeDI 由 Dan Federman 创建,他是 Airbnb 闭源 Swift 依赖注入系统的架构师。在他于 Airbnb 任职后,Dan 开发了 SafeDI,旨在与 Swift 社区分享一个现代的、编译时安全的依赖注入解决方案。

Dan 在维护开源库方面拥有可靠的记录:他与他人共同创建了 Valet,并自 2015 年首次亮相以来一直在维护该存储库。

致谢

特别感谢 @kierajmumick 帮助塑造 SafeDI 的早期设计。