注入 (Inject)

为 Swift 提供轻松的模块化依赖注入。

Unit Tests


为 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)

有两种覆盖依赖项的情况

全局覆盖 (Global override)

当您有多个实现并且想要根据目标使用不同的实例时,请使用全局覆盖。 在这种情况下,您可以使用新策略、实例或注入全局覆盖注入。

// 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))
    }

本地覆盖 (Local override)

您也可以通过直接分配覆盖来直接覆盖仅该实例的依赖项

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")
}

安装 (Installation)

添加依赖项

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

这样,您就可以拥有注入配置的单一事实来源。

有三个新的策略及其各自的范围和生命周期

请根据这些更新的策略和范围审查和调整您的代码。