仓库已存档

为了增强 SwiftUI 中的环境管理,我们推荐使用 SwiftEnvironment! SwiftEnvironment 提供了强大的功能,并且未来会得到支持。

Impose

Impose 是一个简单的 Swift 依赖注入库

codebeat badge build test SwiftPM Compatible Version License Platform

要求

安装

Cocoapods

Impose 可通过 CocoaPods 获得。 要安装它,只需将以下行添加到您的 Podfile 中

pod 'Impose', '~> 3.6.0'

通过 XCode 使用 Swift Package Manager

通过 Package.swift 使用 Swift Package Manager

Package.swift 中添加为您的 target dependency

dependencies: [
    .package(url: "https://github.com/hainayanda/Impose.git", .upToNextMajor(from: "3.6.0"))
]

在您的 target 中将其用作 Impose

 .target(
    name: "MyModule",
    dependencies: ["Impose"]
)

作者

Nayanda Haberty, hainayanda@outlook.com

许可证

Impose 在 MIT 许可证下可用。 有关更多信息,请参见 LICENSE 文件。

基本用法

Impose 非常易于使用和直接,您所需要做的就是为依赖项提供一些 provider

Injector.shared.addSingleton(for: Dependency.self, SomeDependency())

然后使用 property wrapper 或全局函数在您的某些类中使用它

class InjectedByPropertyWrapper {
    @Injected var dependency: Dependency
    
    ...
    ...
}

class InjectedByInit {
    var dependency: Dependency
    
    init(dependency: Dependency = inject(Dependency.self)) {
        self.dependency = dependency
    }
}

provider 是 autoClosure 类型,因此您可以执行以下操作

Injector.shared.addSingleton(for: Dependency.self) {
    let dependency: SomeDependency = .init()
    dependency.doSomeSetup()
    return dependency
}

provider 自动仅通过调用闭包来创建一个实例并重用该实例,因此仅调用一次闭包。 如果您希望 provider 为每次注入调用闭包,则可以使用 addTransient 方法

Injector.shared.addTransient(for: Dependency.self, SomeDependency())

不要忘记,如果尚未注册 provider,它将引发无法捕获的错误。 如果要手动捕获错误,只需改用 tryInject

class InjectedByInit {
    var dependency: Dependency
    
    init(dependency: Dependency? = nil) {
        do {
            self.dependency = dependency ?? try tryInject(for: Dependency.self)
        } catch {
            self.dependency = DefaultDependency()
        }
    }
}

安全注入

有时您只是不希望您的应用程序仅仅因为依赖项注入失败而抛出错误。 在这种情况下,只需使用 @SafelyInjected 属性或 injectIfProvided 函数。 如果注入失败,它将返回 nil

class InjectedByPropertyWrapper {
    @SafelyInjected var dependency: Dependency?
    
    ...
    ...
}

class InjectedByInit {
    var dependency: Dependency
    
    init(dependency: Dependency? = injectIfProvided(for: Dependency.self)) {
        self.dependency = dependency
    }
}

如果注入失败,您始终可以提供要调用的闭包或自动闭包

class InjectedByInit {
    var dependency: Dependency
    
    init(dependency: Dependency? = inject(Dependency.self, ifFailUse: SomeDependency())) {
        self.dependency = dependency
    }
}

Singleton Provider

最简单的注入 Provider 是 Singleton provider。 该 provider 仅创建一个实例,存储它,并重用该实例,因此仅调用一次闭包。 在释放 Injector 之前,不会释放该实例。 这对于共享实例依赖项很有用

Injector.shared.addSingleton(for: Dependency.self, SomeDependency())

Transient Provider

与 Singleton 不同,Transient 完全不会存储依赖项,它只会每次在需要时重新创建依赖项。 但是,闭包将被强烈存储。 这对于不存储任何内容的服务很有用

Injector.shared.addTransient(for: Dependency.self, SomeDependency())

Weak Provider

此 provider 是 singleton 和 transient provider 的组合。 在返回实例之前,它会将实例存储在弱变量中。 一旦存储的实例变为 nil,它将为下一次注入创建一个新实例。 但是,闭包将被强烈存储。 这对于您想要使用和共享但不再使用时释放的依赖项很有用

Injector.shared.addWeakSingleton(for: Dependency.self, SomeDependency())

注入的 DispatchQueue

您可以传递一个 DispatchQueue 实例,以便在解析实例时使用。 例如,如果您的依赖项应从主线程初始化,只需这样做

Injector.shared.addTransient(for: Dependency.self, resolveOn: .main, SomeDependency())

如果您不传递任何内容,它将尝试在注入依赖项解析器时在 DispatchQueue 上解析依赖项。

InjectionEnvironment

您可以为将与该对象一起存在的特定对象定义一个特定环境,并使用 InjectionEnvironment 对象作为依赖项的主要来源

InjectionEnvironment.forObject(myObject)
    .inject(for: Dependency.self, SomeDependency())
    .inject(for: AnotherDependency.self, SomeOtherDependency())

在上面的代码中,myObject Injected propertyWrapper 将使用 InjectionEnvironment 作为依赖项的主要来源。 如果 InjectionEnvironment 未提供依赖项,它将在 Injector.shared 中搜索。

您可以将依赖项 providers 从另一个对象的 InjectionEnvironment 传输到另一个,因此它将使用相似的 InjectionEnvironment

InjectionEnvironment.fromObject(myObject, for: someObject)
  .inject(for: SomeOtherDependency.self, SomeDependency())

在上面的代码中,someObject 将具有一个新的 InjectionEnvironment,其中包含所有 myObject 依赖项 providers,以及稍后添加的 providers。 如果手动分配,它也会从 myObject Injected propertyWrapper 中填充依赖项。

class MyObject { 
    @Injected manual: ManualDependency
    
    init() { 
        // this dependency will be transfered to another InjectionEnvironment created from this object
        manualDependency = MyManualDependency()
    }
}

循环依赖

InjectedSafelyInjected propertyWrapper 将延迟解析依赖项,因此即使您有循环依赖项也可以工作。 但是,如果您使用 inject 函数而不是 init,它将引发堆栈溢出错误,因为它会立即解析依赖项。 即使不建议使用循环依赖项,最好还是使用 propertyWrapper 进行注入,以避免此问题。

多线程注入

Impose 旨在原子地解析依赖项。 因此,只要依赖项是线程安全的,解析将是线程安全的。

一个 Provider 的多个类型

如果需要,您可以为一个 provider 注册多个类型

Injector.shared.addSingleton(for: [Dependency.self, OtherDependency.self], SomeDependency())

或用于 transient

Injector.shared.addTransient(for: [Dependency.self, OtherDependency.self], SomeDependency())

甚至用于环境

InjectionEnvironment.forObject(myObject).inject(for: [Dependency.self, OtherDependency.self], SomeDependency())

多个 Injector

您可以拥有多个 Injector,以便为同一类型提供不同的依赖项

Injector.shared.addTransient(for: Dependency.self, Primary())

let secondaryInjector = Injector()
secondaryInjector.addTransient(for: Dependency.self, Secondary())

要使用另一个 injector,请切换它

Injector.switchInjector(to: secondaryInjector)

切换回来就像调用 void 方法一样容易

Injector.switchToDefaultInjector()

Module Provider

如果您有一个模块化项目,并且希望各个模块自己手动注入所有内容。 您可以使用 ModuleProvider 协议,并将其用作主模块中的 provider

// this is in MyModule
class MyModuleInjector: ModuleProvider {

    func provide(for injector: Injector) {
        injector.addSingleton(for: Dependency.self, SomeDependency())
    }
}

然后让我们说在您的 AppDelegate

import Impose
import MyModule

@main
class AppDelegate: UIResponder, UIApplicationDelegate {
    
    func application(
            _ application: UIApplication,
            didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        provideDependencies()
        // do something
        return true
    }
    
    func provideDependencies() {
        Injector.shared.provide(using: MyModuleInjector())
    }
}

它将使用给定的 Injector 调用 provide(using:)。 您可以根据需要添加任意数量的 ModuleProvider,但是如果 Module 为相同类型的解析器提供相同的依赖项,它将使用新的依赖项覆盖以前的依赖项。

AutoInjectMock

如果您要进行单元测试并需要模拟某些依赖项,则可以在测试准备中跳过注入,并在您的模拟对象上实现 AutoInjectMock,如下所示

class MyServiceMock: MyServiceProtocol, AutoInjectMock { 
    static var registeredTypes: [Any.Type] = [MyServiceProtocol.self]
    ..
    ..
}

然后在您的单元测试中,您可以像这样使用它

serviceMock = MyServiceMock().injected()

// or

serviceMock = MyServiceMock().injected(using: customInjector)

不要忘记,您应该使用一个类实例才能使其正常工作,因为如果您由于其性质而使用 struct,则注入的实例将有所不同。

贡献

您知道该怎么做,只需克隆并提出拉取请求