依赖注入 2.0.2

DependencyInjection 2.0.2

Sebastian Pickl 维护。



DependencyInjection

基于服务定位模式的依赖注入微框架,利用 Swift 的属性包装器。

使用方法

在 App 启动时,通过覆盖 AppDelegate 的初始化器来注册依赖项

import DependencyInjection
import UIKit

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {

    @LazyInject private var config: AppConfiguration
    @LazyInject private var router: Router

    override init() {
        super.init()

        DIContainer.register {
            Shared(AppConfigurationImpl() as AppConfiguration)
            Shared(RouterImpl() as Router)
        }
    }

    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {

        // use `config` and `router`

        return true
    }
}

懒加载与急加载评估

如上所述,由于注入属性的初始化器会先于 AppDelegate 的初始化器调用,因此有必要将注入属性的使用和初始化分开。`@LazyInject` 仅在属性首次访问时解析。要解析属性,请使用 `@Inject`。

可变与不可变注入属性

通过 `@Inject` 和 `@LazyInject` 注入的属性是不可变的,这由编译器强制执行。要用 `@MutableInject` 和 `@MutableLazyInject` 解析可变属性。

可选依赖

在特定情况下无法解析的依赖项可以简单地标记为Optional

@Inject var player: MediaPlayer?

如果没有注册MediaPlayer实例,则player解析为nil。注意:如果将Optional实例注册,则player也解析为nil,因为注册的类型不是预期的MediaPlayer,而是此处的Optional<MediaPlayer>

共享与新实例

将依赖项注册为Shared将始终解析为相同的(相同的)实例。要获取每个属性的新实例,请使用New

DIContainer.register(New(MockRouter() as Router))

这样做可以在生产代码中覆盖注册的依赖项,例如,在不共享对象的情况下在测试中使用mock对象。

别名

还支持使用多个别名协议注册实例,每个协议仅公开其功能的一部分

DIContainer.register(Shared(RouterImpl.init, as: Router.self, DeeplinkHandler.self))

注册具有自身依赖的依赖项

如果注册的依赖项依赖于其他依赖项,应该通过初始化器注入传递,则存在用于注册SharedNew实例的重载,它们在闭包中传递一个Resolver对象

DIContainer.register {
    Shared { resolve in RouterImpl(config: resolve()) }
}

模块

为了对依赖进行分组或避免在Swift模块外暴露具体类型,可以使用依赖注入(DI)模块。这些模块是注册的便捷包装器,可以定义在代码库的不同部分,然后自行注册,例如在AppDelegate中。

// Feature 1

struct FeatureOneDependencyInjection {
    static let module = Module {
        Shared(FeatureOneImplementation() as FeatureOne)
        New(FeatureOneViewModelImplementation() as FeatureOneViewModel)
    }
}

// Feature 2

struct FeatureTwoDependencyInjection {
    static let module = Module(Shared(FeatureTwoImplementation() as FeatureTwo))
}

// AppDelegate

override init() {
    super.init()

    DIContainer.register {
        FeatureOneDependencyInjection.module
        FeatureTwoDependencyInjection.module
    }
}

或者可以在中心位置使用模块,直接进行内联注册。

DIContainer.register {
    Module {
        Shared(FeatureOneImplementation() as FeatureOne)
        New(FeatureOneViewModelImplementation() as FeatureOneViewModel)
    }
    Module {
        Shared(FeatureTwoImplementation() as FeatureTwo)
    }
}

在AppDelegate之外进行替代注册

可以通过实现依赖于DIContainerDependencyRegistering协议以及实现registerDependencies方法来注册依赖。一旦解决第一个依赖项,则注册依赖项。

extension DIContainer: DependencyRegistering {
    public static func registerDependencies() {
        register(Shared(RouterImpl() as Router))
    }
}

没有使用属性包装器的使用方法

由于当前无法在函数体中使用属性包装器,因此依赖项可以“手动”解决。

func foo() {
    DIContainer.resolve(Router.self)
}

或者如果编译器可以推断要解决的类型

func foo() {
    bar(router: DIContainer.resolve())
}

func bar(router: Router) {
    //
}

参数化解析

如果需要在稍后提供某些或所有参数的类型上也应用控制反转,则可以注册一个接收参数并返回所需对象的闭包。在这种情况下,注册一个New实例最有意义,这意味着每次解决依赖项时都会创建一个新对象。如果注册一个Shared实例,则解决的实例将始终是针对相应类型首先解决的实例,而参数将被忽略。

func register() {
    DIContainer.register {
        New({ resolver, id in ConcreteViewModel(id: id) }, as: ViewModelProtocol.self)

        // alternatively:
        New { _, id in ConcreteViewModel(id: id) as ViewModelProtocol }
    }
}

必须提供id参数才能使用实现ViewModelProtocol的实例。第一个参数resolver可以用来或忽略以通过依赖注入解决其他参数。提供参数是在闭包中完成的。

func resolve() -> ViewModelProtocol {
    DIContainer.resolve(ViewModelProtocol.self, arguments: { "id_goes_here" })
}

func resolve() -> PresenterProtocol {
    // multiple arguments are provided as tuple:
    DIContainer.resolve(PresenterProtocol.self) { ("argument 1", 23, "argument 3") }
}

由于Swift中属性(以及相应的属性包装器)的初始化器在self可用之前被调用,因此可以在@Inject@LazyInject的初始化器中提供硬编码的参数。因此,参数化解析当前仅限于如上所示的DIContainerresolve方法。

线程安全性

注册和解析依赖都在一个专门的同步和可重入队列中处理。