Swift 项目的编译时安全依赖注入。SafeDI 为开发者提供手动依赖注入的安全性和简洁性,而无需编写大量的样板代码。
编译时安全
线程安全
分层依赖作用域
构造器注入
多模块支持
依赖反转支持
传递依赖解决
循环检测
架构独立
简单集成:无需 DI 专用类型或泛型
易于测试:每个类型都有一个逐个成员初始化器
清晰的错误消息:永远无需调试生成的代码
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,请按照以下三个步骤操作
您可以在Examples 文件夹中查看示例集成。如果您要将现有项目迁移到 SafeDI,请按照我们的迁移指南进行操作。
要将 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
作为依赖项。
要将 SafeDI 框架作为依赖项添加到使用CocoaPods的包中,请将以下内容添加到您的 Podfile
中
pod 'SafeDI', '~> 1.0.0'
SafeDI 提供了一个名为 SafeDIGenerator
的代码生成插件。此插件可以在有限数量的项目配置上直接使用。如果您的项目不属于这些受支持的配置,您可以配置您的构建以直接使用 SafeDITool
命令行可执行文件。
如果您的第一方代码包含在 .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 包中,您可以将以下行添加到根目标的定义中
plugins: [
.plugin(name: "SafeDIGenerator", package: "SafeDI")
]
您可以在ExamplePackageIntegration 包中看到这种集成实践。
与 SafeDIGenerator
Xcode 项目插件不同,SafeDIGenerator
Swift 包插件可以在无需额外配置步骤的情况下查找依赖模块中的源文件。如果您发现 SafeDI 生成的依赖树缺少所需的导入,您可以创建一个 .safedi/configuration/additionalImportedModules.csv
,其中包含一个逗号分隔的模块名称列表以进行导入。.safedi/
文件夹必须放置在与您的 Package.swift
文件相同的文件夹中。
使用预构建脚本下载 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 的编译时安全性和分层依赖作用域使其类似于 Needle 和 Weaver。与 Needle 不同,SafeDI 不需要为可以在 DI 树中实例化的每种类型定义依赖协议。与 Weaver 不同,SafeDI 不需要定义和维护与您的常规 Swift 代码并存的容器。
其他 Swift DI 库,例如 Swinject 和 swift-dependencies,不提供编译时安全性。与此同时,像 Factory 这样的库确实提供了依赖树的编译时验证,但阻止了分层依赖作用域。这意味着在使用 Factory 时,作用域依赖项(例如网络层中的身份验证令牌)只能有选择地注入。
很高兴您对 SafeDI 感兴趣,我很乐意看到您将其发展成什么样子。在提交 Pull Request 之前,请查看贡献指南。
感谢您参与这个旅程,祝您注入愉快!
SafeDI 由 Dan Federman 创建,他是 Airbnb 闭源 Swift 依赖注入系统的架构师。在他于 Airbnb 任职后,Dan 开发了 SafeDI,旨在与 Swift 社区分享一个现代的、编译时安全的依赖注入解决方案。
Dan 在维护开源库方面拥有可靠的记录:他与他人共同创建了 Valet,并自 2015 年首次亮相以来一直在维护该存储库。
特别感谢 @kierajmumick 帮助塑造 SafeDI 的早期设计。