AnnotationInject

Cocoapods SPM tests twitter

生成你的依赖注入。旨在安全。

AnnotationInject
🗽 让你从手动注册依赖中解放出来。
花更少的时间配置,更多的时间编码!
🛡 不再有运行时崩溃,因为依赖不是最新的。一切都在编译时检查。
👐 基于你喜欢的开源工具,如 SourcerySwinject
📖 100% 开源,基于 MIT 许可证

特定版本的文档可能略有不同。如果您遇到问题,请先查看版本文档(通过在 Github 分支/标签中选择版本)。

注入的问题是什么?

没有注解

使用依赖注入库(例如 Swinject),你需要记住注册你的依赖

container.register(CoffeeMaker.self) { r in
  return CoffeeMaker(heater: r.resolve()!) // Trouble ahead, not sure Heater is in fact registered!
}

/// later in your code
let coffeeMaker = container.resolve(CoffeeMaker.self) // crash, missing Heater dependency!

运行这段代码,我们会在运行时得到一个崩溃:我们没有注册任何 heater,导致 CoffeeMaker 解析器崩溃。

使用注解

注解将生成你的依赖,并确保一切都在编译时解析。

/// sourcery: inject
class CoffeeMaker {
    init(heater: Heater) {

    }
}

这次我们会得到一个编译时错误,因为我们忘记声明 Heater 依赖。万岁!

用法

1. 注解你的依赖

/// sourcery: inject
class CoffeeMaker {
  init(heater: Heater) { }
}

/// sourcery: inject
class Heater {
    init() { }
}

2. 添加构建阶段以生成依赖

请参阅 安装 以了解更多详情。

如果并非所有依赖都可以解析,构建阶段将失败,阻止你的代码成功编译。

3. 添加生成的文件并使用生成的代码

let resolver = Assembler([AnnotationAssembly()]).resolver

// `registeredService` is generated code. It is completely safe at compile time.
let coffeeMaker = resolver.registeredService() as CoffeeMaker
let heater = resolver.registeredService() as Heater

安装

注意:AnnotationInject 依赖/依赖于 Sourcery 进行注解声明,以及 Swinject 作为依赖注入器。

dependencies: [
    .package(url: "https://github.com/pjechris/AnnotationInject.git", from: "0.6.0")
]

然后将 Build phases 添加到你的项目中

swift run annotationinject-cli --sources <path to your sources> --output <path to output generated code> (--args imports=<MyLib1> -args imports=<MyLib2>>)

在 Xcode 中添加 AnnotationInject 作为依赖,然后将此 Build phase 添加到你的项目中

SPM_CHECKOUT_DIR=${BUILD_DIR%Build/*}SourcePackages/checkouts/AnnotationInject
cd $SPM_CHECKOUT_DIR
/usr/bin/xcrun --sdk macosx swift run annotationinject-cli ...

pod AnnotationInject 添加到你的 Podfile,并将新的 Build phases 添加到你的项目中

"$(PODS_ROOT)"/AnnotationInject/Scripts/annotationinject --sources <path to your sources> --output <path to output generated code> (--args imports=<MyLib1> -args imports=<MyLib2>>)

注意:你可以将所有 sourcery 命令行选项传递给 annotationinject 脚本。

  1. 安装 SwinjectSourcery

  2. 复制粘贴 Sources 和 Templates 文件夹,并在你的项目中添加新的 Build phases

sourcery --templates <path to copied templates> --sources <path to your sources> --output <path to output generated code> (--args imports=<MyLib1> -args imports=<MyLib2>>)

可用注解

inject

将类注册到依赖容器中。

/// sourcery: inject
class CoffeeMaker { }
生成的代码

container.register(CoffeeMaker.self) {
  return CoffeeMaker()
}

extension SafeDependencyResolver {
  func registeredService() -> CoffeeMaker {
    return resolve(CoffeeMaker.self)!
  }
}

选项

name
为服务定义一个名称。生成的方法将使用该名称。
scope
请参阅 Swinject 对象作用域
type
定义注册类的类型。当你想要针对协议进行解析时使用它。

/// sourcery:inject: scope = "weak", type = "Maker", name = "Arabica"
class CoffeeMaker: Maker { }

inject (init)

注册一个特定的 init 用于注入。如果未提供注解,则使用第一个找到的 init。

注意:类仍然需要用 inject 注解。

// sourcery: inject
class CoffeeMaker {
  init(heater: Heater) { }

  // sourcery: inject
  convenience init() {
    self.init(heater: CoffeHeater())
  }
}
生成的代码

container.register(CoffeeMaker.self) {
  return CoffeeMaker()
}

extension SafeDependencyResolver {
  func registeredService() -> CoffeeMaker {
    return resolve(CoffeeMaker.self)!
  }
}

inject (attribute)

在 init 之后注入一个属性。属性需要标记为 Optional (?!)。

注意:类仍然需要用 inject 注解。

// sourcery: inject
class CoffeeMaker {
  /// sourcery: inject
  var heater: Heater!

  init() { }
}
生成的代码

container.register(CoffeeMaker.self) {
  return CoffeeMaker()
}
.initCompleted { service, resolver in
  service.heater = resolver.registeredService()
}

provider

使用自定义函数来注册你的依赖。这与手动实现 container.register 并在保持安全性的同时相同。请注意,提供的方法必须被调用 instantiate

注意:如果你正在提供第三方库(例如来自 Cocoapods),你将需要使用 args.imports MyLib,MyLib2,... 命令行参数将这些导入传递给 AnnotationInject。

class CoffeeMaker {
  init(heater: Heater) { }
}

// sourcery: provider
class AppProvider {
  static func instantiate(resolver: SafeDependencyResolver) -> CoffeeMaker {
    return CoffeeMaker(heater: CoffeHeater())
  }
}
生成的代码

container.register(CoffeeMaker, factory: AppProvider.instantiate(resolver:))

extension SafeDependencyResolver {
  func registeredService() -> CoffeeMaker {
    return resolve(CoffeeMaker.self)!
  }
}

provided (0.5.0 版本已不再需要)

声明一个参数作为参数来定义到解析器方法中。适用于 init 和 provider 方法。

注意事项

生成的代码由于缺少导入而无法编译

设置 --args imports=<MyLib1> -args imports=<MyLib2>> 以便生成的代码包含第三方库。

Foundation 类型 (URLSession, NSNotificationCenter, ...) 在生成的代码中为空 (.self)

Sourcery 尚无法找到这些类型。因此,它们被视为不存在。解决方法:在 Provider 内部定义周围的类型,并为其提供 Foundation 类型。

构建阶段失败,但没有报告错误

这可能是由于 Sourcery 与 Xcode 11.4 存在一些不兼容性。解决方法:使用 Homebrew 安装 Sourcery,然后添加到构建步骤 SOURCERY_BINPATH=sourcery 作为环境变量。

Pods/Sourcery/bin/Sourcery.app/Contents/MacOS/Sourcery: No such file or directory

你可能正在使用 Sourcery 作为 Cocoapods 依赖项,但不幸的是,这并非总是有效。解决方法:使用 Homebrew 安装 Sourcery,然后添加到构建步骤 SOURCERY_BINPATH=sourcery 作为环境变量。

License

本项目根据 MIT 许可证发布。请参阅 LICENSE 文件以了解详情。