一个 Stitcher 的支持包,它使用 Swift 宏定义元编程实用程序,从而能够自动为函数和初始化器进行参数注入,以及为自动依赖项注册提供实用程序。
目录
StitcherMacros 至少需要 iOS 13、macOS 10.15、tvOS 13 或 watchOS 6 以及 Swift 版本 5.9。
安装后,StitcherMacros 将自动安装并导出 Stitcher。
您可以使用 Xcode 11.0 或更高版本将 Stitcher 添加为 Swift Package 依赖项,方法是选择 File > Swift Packages > Add Package Dependency...
或 Xcode 13.0 及更高版本中的 File > Add packages...
,然后添加以下 URL
https://github.com/athankefalas/StitcherMacros.git
您也可以通过下载 StitcherMacros
项目并将其包含在您的项目中来手动安装此库。
本节中讨论的一些项目基于您熟悉 Stitcher 库中定义的基本概念。
参数注入是指在类型的初始化器中或在调用函数时,将类型的依赖项作为参数注入的做法。 为了自动生成初始化器或函数,并使它们的参数由 Stitcher
自动注入,目标 init
或 func
声明必须使用 @InjectedParameters
宏进行标记。
class UploadService {
private let networkingSession: NetworkingSession
@InjectedParameters
init(networkingSession: NetworkingSession) {
self.networkingSession = networkingSession
}
}
它将被扩展为
class UploadService {
private let networkingSession: NetworkingSession
@InjectedParameters
init(networkingSession: NetworkingSession) {
self.networkingSession = networkingSession
}
/// Automatically generated convenience initializer
convenience init() {
self.init(
networkingSession: try! DependencyGraph.inject(
byType: NetworkingSession.self
)
)
}
}
类似地,相同的模式可以用于函数,其中某些或所有参数被注入
class UploadService {
@InjectedParameters(ignoring: "file")
func upload(file: URL, networkingSession: NetworkingSession) {}
}
这将扩展为以下内容
class UploadService {
@InjectedParameters(ignoring: "file")
func upload(file: URL, networkingSession: NetworkingSession) {}
/// Automatically generated function when macro is expanded:
func upload(file: URL) {
upload(
file: file,
networkingSession: try! DependencyGraph.inject(
byType: NetworkingSession.self
)
)
}
}
InjectedParameters
宏可以使用以下参数进行配置
parent
父类型的种类。 当定义具有注入参数的初始化器时,它用于确定要创建的初始化器的种类。 默认情况下,此选项设置为类父类型,这将创建一个便捷初始化器。 当在函数上使用宏时,此参数将被忽略。
strategy
用于在生成的初始化器 - 函数中注入依赖项的策略。
ignoring
任何将被忽略的参数的名称,这些参数将不会被注入,而是由生成的初始化器 - 函数的调用者手动传递。 默认情况下,原始声明中存在的任何参数都不会被忽略。
当与类型初始化器一起使用时,父类型参数用于控制宏输出。 对于 Swift 版本 <=
5.10,编译器插件引擎不提供附加父类型的 peer 宏(例如 InjectedParameters
)的信息。 因此,当在 Swift 6 之前的 Swift 版本中将此宏添加到 struct
或 enum
初始化器时,还必须提供父类型种类,以避免在生成的代码中出现编译时错误。
可接受的父类型值的定义
/// The kind of parent an attached peer macro has.
public enum AttachedParentKind {
case actorParent
case classParent
case enumParent
case structParent
}
此参数的默认值为 .classParent
,它与 class
和 actor
类型兼容,因为它们都是引用类型,并且辅助初始化器可以以相同的方式定义为 convenience
初始化器。
在结构体初始化器上使用 InjectedParameters
struct Service {
let repository: EntityRepository
@InjectedParameters(parent: .structParent)
init(repository: EntityRepository) {
self.repository = repository
}
}
strategy 参数控制依赖项的注入方式,按类型或按名称。 默认情况下,生成代码中的所有依赖项都将使用 .stitcherByType
策略注入。
class Service {
@InjectedParameters(strategy: .stitcherByType)
func findAll(in repository: EntityRepository) -> [Entity] { [] }
/// Automatically generated function when macro is expanded:
func findAll() -> [Entity] {
findAll(
in: try! DependencyGraph.inject(
byType: EntityRepository.self
)
)
}
@InjectedParameters(strategy: .stitcherByType)
func clear(_ repository: EntityRepository) {}
/// Automatically generated function when macro is expanded:
func clear() {
clear(
try! DependencyGraph.inject(
byType: EntityRepository.self
)
)
}
}
为了使用按名称注入,可以改为使用 .stitcherByName
策略。 注入期间使用的名称是原始声明中使用的参数名称。
class Service {
@InjectedParameters(strategy: .stitcherByName)
func findAll(in repository: EntityRepository) -> [Entity] { [] }
/// Automatically generated function when macro is expanded:
func findAll() -> [Entity] {
findAll(
in: try! DependencyGraph.inject(
byName: "repository"
)
)
}
@InjectedParameters(strategy: .stitcherByName)
func clear(_ repository: EntityRepository) {}
/// Automatically generated function when macro is expanded:
func clear() {
clear(
try! DependencyGraph.inject(byName: "repository")
)
}
}
忽略的参数是一个参数名称序列,用于控制哪些初始化器 - 函数参数将被宏忽略,并在调用时手动注入。
class Service {
@InjectedParameters(
ignoring: "imageData", "attributes"
)
func upload(
imageData: Data,
convertedWith attributes: ImageAttributes = [],
processingService: ImageProcessingService
) async throws {}
/// Automatically generated function when macro is expanded:
func upload(
imageData: Data,
convertedWith attributes: ImageAttributes = []
) async throws {
try await upload(
imageData: imageData,
convertedWith: attributes,
processingService: try! DependencyGraph.inject(
byType: ImageProcessingService.self
)
)
}
}
@InjectedParameters
宏支持所有具有非泛型参数的初始化器 - 函数。 如果初始化器 - 函数具有泛型参数,并且需要对其余参数进行注入,则必须使用 ignored
参数显式忽略该泛型参数。
请注意,所有生成的初始化器 - 函数都标有 @_disfavoredOverload
属性,并且比显式声明的变体具有更低的调用优先级。 此外,作为类型首选初始化器也支持的生成的初始化器会自动使用 @PreferredInitializer
宏进行标记。
@PreferredInitializer
宏是一个标记属性,用于声明应在 @Registerable
宏自动生成依赖项注册期间使用哪个初始化器。
首选初始化器支持任何没有泛型参数、不会通过返回 nil 或抛出错误而失败,并且是同步的初始化器。
可以使用 @Registerable
宏自动生成依赖项注册,并通过遵循 RegisterableDependency
协议将其作为静态成员附加到指定的类型。
@Registerable
class Service {
init() {}
}
// Expanded macro:
extension Service: RegisterableDependency, AutoregisterableDependency {
static let dependencyRegistration = GeneratedDependencyRegistration<Service>(
locator: .type(Service.self),
scope: .automatic(for: Service.self),
eagerness: .lazy
) {
Service()
}
}
此外,如果依赖项通过名称或关联值进行定位,则定位器原始值也会自动添加到一致性中
@Registerable(by: .name("service"))
class Service {
init() {}
}
// Expanded macro:
extension Service: RegisterableDependency, AutoregisterableDependency {
static let dependencyName: String = "service"
static let dependencyRegistration = GeneratedDependencyRegistration<Service>(
locator: .name("service"),
scope: .automatic(for: Service.self),
eagerness: .lazy
) {
Service()
}
}
在运行时,可以将注册添加到依赖项容器,如下所示
@main
struct SomeApp: App {
@Dependencies
var container = DependencyContainer {
// Dependency registration
Service.dependencyRegistration
}
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
可以使用与使用 Dependency
结构的基本依赖项注册相同的选项来配置 @Registerable
宏。
控制依赖项的定位方式。 默认情况下,依赖项将按其类型进行定位。
控制依赖项的作用域。 默认情况下,将使用的依赖项作用域是 .automatic()
。
控制依赖项的急切性。 默认情况下,将使用的依赖项急切性是 .lazy
。
可以使用 @PreferredInitializer
宏来消除歧义,以确定将选择哪个初始化器来实例化依赖项。 如果发现多个首选初始化器,将选择参数数量最少的初始化器。
Stitcher Macros 包含一个模板,可以选择与 Sourcery 一起使用,以便在构建时从所有使用 @Registerable
宏标记的依赖项动态生成一个依赖项组。
此外,还可以使用完全自定义的模板,无论是通过定位 @Registerable
宏属性还是 AutoregisterableDependency
协议。
Sourcery/sourcery.yml
中找到一个简单的示例配置文件。DependencyContainer
中,以在运行时注册依赖项。@main
struct SomeApp: App {
@Dependencies
var container = DependencyContainer {
// Autogenerated Dependency registrations
DependencyGroup.autoregisteredDependencies
}
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
Sourcery
集成是可选的,提供的模板和配置旨在作为简单的启动点,因此建议对其进行修改,以更好地适应特定项目的构建阶段/管道。
如果您在使用该库时遇到问题,或者有功能请求,请务必提出问题。