发布 3.6.0

Impose 3.6.0

nayanda1 维护。



Impose 3.6.0

  • 作者:
  • nayanda

Impose

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

codebeat badge build test SwiftPM Compatible Version License Platform

要求

  • Swift 5.0 或更高版本(或当使用 Swift Package Manager 时为 5.3)
  • iOS 9.3 或更高版本(或当使用 Swift Package Manager 时为 10)
  • macOS 10.10 或更高版本
  • tvOS 10 或更高版本
  • WatchOS 4 或更高版本

安装

CocoaPods

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

pod 'Impose', '~> 3.4.0'

从 XCode 开始使用 Swift 包管理器

  • 使用 XCode 菜单中的 文件 > Swift 包 > 添加包依赖 并将其添加
  • https://github.com/hainayanda/Impose.git 作为 Swift 包 URL 添加
  • 版本 中设置规则,选择 直到下一个主要版本 选项,并将其版本设置为 3.4.0
  • 点击下一步并等待

从 Package.swift 使用 Swift 包管理器

Package.swift 中将其添加为目标依赖项

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

在您的目标中将其用作 Impose

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

作者

Nayanda Haberty, [email protected]

许可协议

Impose 遵守 MIT 许可协议。有关更多信息,请参阅 LICENSE 文件。

基本用法

Impose的使用非常简单直观,您只需要提供一些依赖项提供商

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

然后在您的某些类中使用属性包装器或使用全局函数来使用它

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

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

提供者是autoClosure类型的,所以您可以这样做

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

提供者将自动只从调用闭包创建一个实例,并重复使用该实例,因此闭包只调用一次。如果想要提供者对每个注入调用闭包,可以使用addTransient方法

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

别忘了,如果提供者尚未注册,它将抛出一个不可捕获的错误。如果想要手动捕获错误,只需使用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
    }
}

单例提供者

最简单的注入提供者是单例提供者。该提供者只创建一个实例,存储它并重用该实例,因此闭包只调用一次。实例将不会释放,直到注入器释放。它对于共享实例依赖项非常有用

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

瞬态依赖提供者

与单例(Singleton)不同,瞬态依赖不会存储依赖,每次需要时都会重新创建依赖。不过闭包将被强存储。这对于存储无数据的服务很有用。

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

弱依赖提供者

此提供者是单例和瞬态提供者的组合。它将在返回之前将实例存储在弱变量中。一旦存储的实例变为nil,它将为下一次注入重新创建一个新的实例。不过闭包将强存储。这对于那些希望在不再使用时释放的依赖项很有用。

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

环境

使用Environment对象,您可以定义特定对象的可特定环境,该环境将伴随该对象并成为依赖项的主要来源。

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

在上面的代码中,myObjectInjected属性包装器将使用环境作为依赖项的主要来源。如果依赖项未由环境提供,它将搜索Injector.shared

您可以将一个对象的依赖项提供者从另一个对象的Environment转移到另一个,以便它使用相似的环境。

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

在上面的代码中,someObject将有一个新的环境,其中包含myObject的所有依赖项提供者,还包含稍后添加的一个。如果手动分配,它还将填充来自myObject Injected属性包装器的依赖项。

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

循环依赖性

InjectedSafelyInjected属性包装器会懒加载依赖,因此即使你有循环依赖它也能工作。但是如果你使用inject函数而不是init进行解析,它将立即解析依赖,这将引发堆栈溢出错误。尽管循环依赖不推荐使用,但使用属性包装器进行注入以避免这个问题会更好。

单一提供者的多种类型

如果你需要,可以为单个提供者注册多个类型

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

或者为短暂需要注册

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

甚至为环境

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

多重注入器

你可以为同一种类型的依赖有多个Injector来提供不同依赖

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

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

要使用其他注入器,只需切换即可

Injector.switchInjector(to: secondaryInjector)

要切换回原来的,就像调用一个空方法一样简单

Injector.switchToDefaultInjector()

模块提供者

如果你有一个模块化项目,并希望每个模块都能自己手动注入一切。你可以使用ModuleProvider协议,并在主模块中将其用作提供者

// 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,但如果模块提供了同一类型解析器的相同依赖项,它将使用新的一个覆盖先前的。

AutoInjectMock

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

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

然后在您的单元测试中,您可以使用它如下所示

serviceMock = MyServiceMock().injected()

// or

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

别忘了,您应该使用类实例来使它生效,因为如果您使用的是结构体,由于其性质,注入的实例将不同。

Contribute

你知道怎么做,只需克隆并提交拉取请求