GitHub license xcode-version Swift 5.0 Carthage Compatible

Hopoate 是一个轻量级的 iOS 依赖注入框架,可以简化应用程序依赖项的注册和解析。

依赖注册

注册依赖项非常简单,只需在 Hopoate 的共享容器上调用一个函数即可。

DependencyContainer.shared.register(AnalyticsProvider(), for: AnalyticsProviding.self)

在这里,我们为 AnalyticsProviding 协议注册了一个 AnalyticsProvider 实例。此 AnalyticsProvider 实例会被容器缓存,并且每当容器被请求与 AnalyticsProviding 匹配的依赖项时,都会返回该实例。

依赖解析

解析依赖项同样简单

let analyticsProvider = DependencyContainer.shared.resolve(AnalyticsProviding.self)

这将解析我们之前注册的符合 AnalyticsProviding 的依赖项。

为了方便起见,提供了一个属性包装器来自动解析依赖项

@Dependency private var analyticsProviding: AnalyticsProviding

此属性访问相当于 DependencyContainer.shared.resolve(AnalyticsProviding.self)

可选依赖解析

对于可能并非始终注册的依赖项,提供了一个 optionalResolve 函数

let optionalDependency = DependencyContainer.shared.optionalResolve(Maybe.self)
optionalDependency?.perhaps()

这些也可以使用属性包装器访问

@OptionalDependency private var maybe: Maybe?

取消注册依赖项

注册依赖项时,会返回一个不透明的注册令牌,该令牌稍后可用于从容器中删除注册

let token = DependencyContainer.shared.register(service: AnalyticsProviding.self) {
    return AnalyticsProvider()
}

/**
Perform work that depends on the registered dependency
*/

DependencyContainer.shared.remove(token)

测试

Hopoate 非常适合单元测试,因为它使用 LIFO 系统进行依赖项解析,即最近注册的给定类型的依赖项是在请求依赖项解析时返回的依赖项。 这允许将模拟实现注册到容器以进行测试,并在测试结束后将其删除。 删除模拟依赖项后,将解析先前为请求的类型注册的任何依赖项。

let mockLogger = MockLogger()
let token = DependencyContainer.shared.register(service: LogProviding.self) {
    return mockLogger
}

testedObject.doSomethingThatLogs()
XCTAssertTrue(mockLogger.receivedLogMessage)

DependencyContainer.shared.remove(token)

可以使用来自 HopoateTestingHelpers 库的 MockContainer 来简化注册和取消注册模拟依赖项的过程(SPM 用户需要使用单独的 HopoateTestingHelpersPackage 包)。 模拟容器使用依赖项容器为给定协议注册一个模拟对象,并在取消分配时取消注册模拟对象。 这是一个例子

import XCTest
import HopoateTestingHelpers
@testable import MyApp

final class MyViewControllerTests: XCTestCase {
    private var myViewController: MyViewController!
    private var mockAnalyticsContainer: MockContainer<AnalyticsProviding, MockAnalyticsProvider>!
    
    override func setUp() {
        super.setUp()
        mockAnalyticsContainer = MockContainer(MockAnalyticsProvider())
    }
    
    override func tearDown() {
        myViewController = nil
        mockAnalyticsContainer = nil
        super.tearDown()
    }
    
    func testItSendsAMessageToTheAnalyticsProviderWhenTheButtonIsTapped() {
        givenAViewController()
        whenTheUserTapsTheButton()
        XCTAssertEqual(mockAnalyticsContainer.mock.receivedMessage, "button_tapped")
    }
    
    private func givenAViewController() {
        myViewController = MyViewController()
    }
    
    private func whenTheUserTapsTheButton() {
        myViewController.button.sendActions(for: .primaryActionTriggered)
    }
}

我们有一个 MockContainer,它将为 AnalyticsProviding 协议注册一个依赖项。 它将使用 MockAnalyticsProvider 的实例作为依赖项。 在我们的 setUp 方法中,我们创建容器和模拟分析提供程序。

稍后,在我们的测试函数中,我们执行一些代码,这些代码使用注册到依赖项容器的 AnalyticsProviding 依赖项,在这种情况下,当用户点击按钮时。 按钮点击发生后,我们可以通过从模拟容器的 mock 属性访问它,来检查我们的模拟依赖项是否已收到我们期望的内容。

测试运行后,将执行 tearDown 函数,该函数将我们的 mockAnalyticsContainer 设置为 nil。 反过来,这会从依赖项容器中删除 MockAnalyticsProvider,使容器保持在测试运行之前的状态。

依赖缓存

默认情况下,当服务注册到依赖项容器时,容器会缓存在创建闭包中给出的服务。

DependencyContainer.shared.register(service: AnalyticsProviding.self) {
    return AnalyticsProvider() // <- This object will be returned by all calls to DependencyContainer.shared.resolve(AnalyticsProviding.self)
}

如果您不想要此缓存行为,则可以在注册依赖项时禁用它

DependencyContainer.shared.register(service: AnalyticsProviding.self, cacheService: false) {
    return AnalyticsProvider() // <- A new instance of AnalyticsProvider will be provided to each call to DependencyContainer.shared.resolve(AnalyticsProviding.self)
}

安装

Swift Package Manager

将以下内容添加到您的包清单中包的依赖项中

.package(name: "Hopoate", url: "https://github.com/darjeelingsteve/hopoate", from: "1.0.0"),

Carthage

将以下内容添加到您的 Cartfile

github "darjeelingsteve/Hopoate"