为 Swift 提供轻松的模块化依赖注入。
为 Swift 提供轻松的模块化依赖注入。
Inject 是一种有主见的依赖倒置方法,旨在使 Swift 项目中的依赖注入变得轻松且无错误。 它允许您在应用程序中倒置依赖关系,以便您可以自由地在模块之间移动注入点,而不会出现任何问题。 Inject 的 API 专注于简洁性和模块化,同时使用 @MainActor 确保线程安全。
考虑一个带有函数 doWork 的协议 Service。 此服务被提取到 Service 包中,以及其当前的实现 ServiceImplementation。
// ServicePackage
public protocol Service {
func doWork()
}
public final class ServiceImplementation: Service {
func doWork() { ... }
}
在您的应用程序中,您想使用 ServiceImplementation,但可以选择将其替换为用于测试或预览的模拟实现。 为此,您需要发布实现的注入
@MainActor public let serviceShared = Injection<Service>(
strategy: .shared,
instance: ServiceImplementation()
)
这创建了一个注入点,该注入点实例化 ServiceImplementation 一次,并在没有人使用该实例时将其释放。
要在您的应用程序中使用它,您需要 Instance
属性包装器
import ServicePackage
@Instance(ServicePackage.serviceShared) var service
...
{
service.doWork()
}
}
在您导入发布注入的包的任何地方,您都可以拥有依赖项的适当实例。
在新版本的 Inject 中,有三种策略可用于管理依赖注入
共享 (Shared): 此策略创建一个共享实例,该实例在所有使用依赖项的对象中重用。 当最后一个引用它的对象被释放时,该实例将被释放。
@MainActor let sharedDependency = Injection<DependencyType>(
strategy: .shared,
instance: DependencyImplementation()
)
单例 (Singleton): 此策略创建一个共享实例,该实例在所有使用依赖项的对象中重用。 该实例将保留在内存中,直到应用程序终止。
@MainActor let singletonDependency = Injection<DependencyType>(
strategy: .singleton,
instance: DependencyImplementation()
)
按需 (On Demand): 此策略为每个使用依赖项的对象创建一个新实例。 当保存依赖项的对象被释放时,每个实例将被释放。
@MainActor let onDemandDependency = Injection<DependencyType>(
strategy: .onDemand,
instance: DependencyImplementation()
)
根据您的特定用例和依赖项管理要求选择适当的策略。
有两种覆盖依赖项的情况
当您有多个实现并且想要根据目标使用不同的实例时,请使用全局覆盖。 在这种情况下,您可以使用新策略、实例或注入全局覆盖注入。
// Overriding the instance
ServicePackage.serviceShared.override(with: { _ in MockService() })
// Overriding the instance and strategy
PackageA.sharedService.override(with: { _ in MockService() }, strategy: .onDemand)
// Overriding the instance and strategy, using the instance provided before override
PackageA.sharedService.override(
with: { service in
service.setKey("someapikey")
return service
},
strategy: .onDemand
)
// Overriding with another injection
@MainActor public let serviceLocal = Injection<Service>(strategy: .onDemand, instance: ServiceImplementation())
PackageA.sharedService.override(with: serviceLocal)
在您的测试中,您也可以在完成测试后回滚 (rollback) 覆盖
func test_Override_WithInjection_Rollback_Global() {
var sut = Consumer()
let override = Injection<PackageA.Service>(strategy: .shared, instance: self.testInjection)
PackageA.sharedService.override(with: override)
XCTAssertEqual(sut.perform(), "test")
XCTAssertEqual(ObjectIdentifier(sut.service), ObjectIdentifier(testInjection))
PackageA.sharedService.rollbackOverride()
sut = Consumer()
XCTAssertNotEqual(ObjectIdentifier(sut.service), ObjectIdentifier(testInjection))
}
您也可以通过直接分配覆盖来直接覆盖仅该实例的依赖项
import ServicePackage
class Consumer {
@Instance(ServicePackage.serviceShared) var service
func perform() -> String {
service.doWork()
}
}
func test_Override_Local() {
let sut = Consumer()
sut.service = MockService()
// now MockService will be used by consumer
XCTAssertEqual(sut.perform(), "test")
}
添加依赖项
Inject 专为 Swift 5 设计。 要依赖 Inject 包,您需要在 Package.swift 中声明您的依赖项
.package(url: "https://github.com/MaximBazarov/Inject.git", from: "1.0.0")
Inject 1.0.0 现在已弃用。 我们犯了一个错误,将注入配置放在了消费者端,使用了 @Injected。 这种方法导致将注入的责任转移到客户端,这是没有意义的,因为您必须自己同步所有注入。 如果您使用此方法,请将配置移动到注入。 将 @Injected 属性包装器替换为 @Instance 并引用适当的已发布的 Injection
例如,如果您有
extension DefaultValues {
var networking: Networking {
HTTPNetworking()
}
}
...
@Injected(\.networking, scope: .local, lifespan: .temporary) var network
将其更改为
// next to the implementation
@MainActor let networking = Injection<Networking>(
strategy: .onDemand,
instance: NetworkingImplementation()
)
...
// usage
@Instance(networking) var network
这样,您就可以拥有注入配置的单一事实来源。
有三个新的策略及其各自的范围和生命周期
.shared
: 范围: .shared
, 寿命: .temporary
.singleton
: 范围: .shared
, 寿命: .permanent
.onDemand
: 范围: .local
, 寿命: .temporary
请根据这些更新的策略和范围审查和调整您的代码。