这是一个名为 "Macaroni" 的 Swift 依赖注入框架。将意大利面条式代码切成碎片!:–)
当我开始我的项目时,我需要某种 DI。 显然,属性包装器可以用于 DI 框架。 这就是它。
Macaroni 使用了这篇文章中的一个技巧 https://www.swiftbysundell.com/articles/accessing-a-swift-property-wrappers-enclosing-instance/ 以便能够访问封闭对象的 self
。 因此存在一个限制:@Injected
*只能在引用类型中使用*,因为属性在第一次访问时会被延迟初始化,从而改变了容器(这对值类型来说是有问题的)。
请查看 UPDATE.md 了解有关迁移的信息。
请使用 Swift Package Manager。 仓库地址:git@github.com:bealex/Macaroni.git
或 https://github.com/bealex/Macaroni.git
。 包的名称是 Macaroni
。
当前版本是 v4.x
// Create the container.
let container = Container()
// Set it as a singleton for the simplest service-locator style resolution.
Container.lookupPolicy = .singleton(container)
// Add service implementations into the container.
let myService = MyServiceImplementation()
container.register { () -> MyService in myService }
// Use it in code.
let myService: MyService = container.resolve()
// Or use it with property wrapper.
class MyClass {
@Injected
var service: MyService
}
首先,让我们导入 Macaroni 并准备好我们要注入的协议和实现。
import Macaroni
protocol MyService {}
class MyServiceImplementation: MyService {}
Macaroni 应该知道容器的位置,以便获取用于注入的对象。 您可以将容器视为一个保存所有对象的盒子。 容器的位置由 Container.lookupPolicy
定义。 让我们使用简单的 服务定位器策略,该策略使用 singleton
对象来保存所有可以注入的对象。
let container = Container()
Container.lookupPolicy = .singleton(container)
为了在容器内部注册一些东西,我们注册一个解析器。 解析器是一个闭包,它返回特定类型的实例。 它可以一直返回相同的实例,也可以在每次访问时创建它。 你来选择。 现在让我们注册解析器,它每次使用都返回相同的实例。
let myService = MyServiceImplementation()
container.register { myService }
然后我们可以像这样注入这个值
class MyClass {
@Injected
var myService: MyServiceImplementation
}
通常我们需要能够像这样使用它:var myService: MyService
,而不是使用实现类型(var myService: MyServiceImplementation
)。 为此,我们需要告诉 Container
,如果它被要求提供 MyService
,它应该注入这个特定的对象。 可以使用以下两个选项之一完成
// 1.
// Now myService is of type `MyService` and registration will be
// typed as `() -> MyService` instead of `() -> MyServiceImplementation`
let myService: MyService /* <- Magic happens here */ = MyServiceImplementation()
container.register { myService }
// 2.
// or like this (I prefer this option):
let myService = MyServiceImplementation()
container.register { () -> MyService /* <- Magic happens here */ in myService }
请注意,注入是延迟发生的,不是在
MyController
初始化期间,而是在首次访问myService
时。
在上面的代码中,实现是立即创建的。 如果您想延迟创建应该注入的对象,您可以使用这样的包装器
class LazilyInitialized<Type> {
lazy var value: Type = { resolver() }()
private let resolver: () -> Type
init(resolver: @escaping () -> Type) {
self.resolver = resolver
}
}
let willBeInstantiatedOnFirstAccess = LazilyInitialized { MyServiceImplementation() }
container.register { () -> MyService in willBeInstantiatedOnFirstAccess.value }
// 1.
// Lazy injection from the container that is captured on initialization, determined by `Container.policy`:
@Injected
var property: Type
// 2.
// Lazy injection from the container that is captured on initialization (you specify it):
@Injected(.capturingContainerOnInit(from: container))
var property: Type
// 3.
// Lazy capturing of the container and resolving:
@Injected(.lazily)
var property: Type
// 4.
// Eager resolving, during the initialization, from the container from `Container.policy`:
@Injected(.resolvingOnInit())
var property: Type
// 5.
// Eager resolving, during the initialization, from the specified container:
@Injected(.resolvingOnInit(from: container))
var property: Type
请注意,参数化注入仅在延迟解析对象时才有效。 积极注入只能按类型(以及如果提供了替代方案)解析对象。
而且延迟注入不能在
structs
中使用,因为它需要在解析后修改对象。
// - create alternative identifier. Strings must be different for different types.
extension RegistrationAlternative {
static let another: RegistrationAlternative = "another"
}
// - registration
container.register(alternative: .another) { () -> MyService in anotherInstance }
// - injection
@Injected(alternative: .another)
var myServiceAlternative: MyService
从 Swift 5.5 开始,我们也可以将属性包装器用于函数参数。 这是函数声明
func foo(@Injected service: MyService) { /* Use service here */ }
以及使用默认实例的调用
foo($service: container.resolved())
或替代实例
foo($service: container.resolved(alternative: .another))
如果您需要使用包含注入属性的对象,您可以像这样从注册闭包内部获取
container.register { enclosing -> String in String(describing: enclosing) }
此解析器仅适用于延迟注入。
使用属性包装器时,您不能使用 weak
(或 lazy
或 unowned
)。 如果你需要这个,你可以使用 @InjecteadWeakly
。
@InjectedWeakly
var myService: MyService?
对于特定封闭对象的属性,有三种容器选择策略
singleton
,可以像这样设置:Container.lookupPolicy = .singleton(myContainer)
。Containerable
协议,该协议定义了对象的 Container
。 您可以使用 .enclosingType(default:)
进行设置。ContainerLookupPolicy
。如果您的应用程序使用多个模块,并且每个模块都需要自己的 Container
,则可以使用此选项
在通用模块中的某个地方编写此代码
protocol ModuleDI: Containerable {}
Container.lookupPolicy = EnclosingTypeContainer()
并在每个模块中编写此代码
private var moduleContainer: Container!
extension ModuleDI {
var container: Container! { moduleContainer } // now each module does have its own container
}
class MyClass: ModuleDI {
@Inject var service: MyService // will be injected from the `moduleContainer`
}
Macaroni 不对多线程做任何处理。 如果需要,请自行处理。
默认情况下,Macaroni 会将简单的事件(容器创建、解析器注册、注入)打印到控制台。 如果您不需要它(或需要以某种方式更改日志),请将 Macaroni.Logger
设置为您的 MacaroniLogger
实现
class MyMacaroniLogger: MacaroniLogger {
func log(/* Parameters */) { /* Logging code */ }
func die() -> Never { /* Log and crash */ }
}
Macaroni.logger = MyMacaroniLogger()
使用此代码完全禁用日志记录
Macaroni.logger = DisabledMacaroniLogger()
许可证:MIT,https://github.com/bealex/Macaroni/blob/main/LICENSE