InjectGrail 0.2.6

InjectGrail 0.2.6

由以下人员维护:Łukasz KwoskaSwingDev iOS CIMateusz FidosŁukasz KwoskaDamian Danielczyk



InjectGrail

Version License Platform

该项目完全功能正常,但需要在几个方面给予很多关注

  • 文档,
  • 示例,
  • 测试,
  • 其他与过程相关的材料,
  • 对生成的代码中的注释和其他可读性改进,
  • Sourcery 模板的可读性改进,
  • 基本框架信息。为什么,灵感等...

如果你愿意帮忙,我们非常欢迎!我们欢迎 PR。

TL;DR

这是

class MessagesViewController: UIViewController {
    private let networkProvider: NetworkProvider
    private let authProvider: AuthProvider
    private let localStorage: LocalStorage
    private let viewModel: MessagesViewModel

    init(networkProvider: NetworkProvider, authProvider: AuthProvider,  localStorage: LocalStorage, ...) {
       self.networkProvider = networkProvider
       self.authProvider = authProvider
       self.localStorage = localStorage
       self.viewModel = MessagesViewModel(networkProvider: networkProvider, authProvider: authProvider, localStorage: localStorage, ...)
    }
}
// ------------------------------------------------------------------
class MessagesViewModel {
     let networkProvider: NetworkProvider
     let authProvider: AuthProvider
     let localStorage: LocalStorage

    init(networkProvider: NetworkProvider, authProvider: AuthProvider,  localStorage: LocalStorage, ...) {
       self.networkProvider = networkProvider
       self.authProvider = authProvider
       self.localStorage = localStorage
       self.authProvider.checkifLoggedIn()
    }
}

变成这样了

protocol MessagesViewControllerInjector: Injector {
}

 class MessagesViewController: UIViewController, Injectable, InjectsMessagesViewModelInjector {
    let injector: MessagesViewControllerInjectorImpl
    init(injector: MessagesViewControllerInjectorImpl) {
       self.injector = injector
       self.viewModel = MessagesViewModel(inject())
    }
}
// ------------------------------------------------------------------
 protocol MessagesViewModelInjector: Injector {
    var networkProvider: NetworkProvider {get}
    var authProvider: AuthProvider {get}
    var localStorage: LocalStorage {get}
 }

class MessagesViewModel: Injectable {
    let injector: MessagesViewModelInjectorImpl
    init(injector: MessagesViewModelInjectorImpl) {
       self.injector = injector
       self.authProvider.checkifLoggedIn()
    }
}
  • 对于你声明的每个类,只包含它需要的依赖项。不是它的子类。
  • 你不会得到一个大袋子,里面有所有你要携带到你的项目中所有类的依赖项。
  • 依赖项将通过层次结构自动推送,而不接触父类定义,
  • 每个类的 init 中只包含它真正需要或其子类(包裹在一个简单的结构体中)的依赖项,
  • 你的类仍然可以手动构建,
  • inject 函数接受当前类中未找到但由子类需要的依赖项作为参数,
  • 没有一行魔法。你可以通过 Cmd+Click 来查看确切的定义。只使用 DI 协议,结构体和扩展来实现。
  • 所有内容的命令补全。

术语摘要

  • Injector - 类的依赖项规范
  • Injectable - 需要其依赖项进行注入的类(通过 init 中的 Injector 进行注入)
  • InjectsXXX - 需要实现希望注入 XXX 注入器的父类。
  • RootInjector - 实现此协议的类或结构体会自动能够注入所有 Injectors。这是注入树的顶端。必须正好有一个类实现此协议。

要求

安装

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

pod 'InjectGrail'

用法

  1. import InjectGrail

  2. 对于每个需要被标记为 Injectable(而不是直接传递参数到 init)的类,创建一个将指定它们的协议,让它遵守 Injector 协议。

    例如,假设我们有一个 MessagesViewModel,我们希望它是可注入的。

    class MessagesViewModel {
       let networkManager: NetworkManager
       
       init(networkManager: NetworkManager) {
           self.networkManager = networkManager
       }
    }

    我们需要创建 MessagesViewModelInjector - 名称不重要。按照惯例,我们使用 <InjectableClassName>Injector 并让它遵守 Injector

        protocol MessagesViewModelInjector: Injector {
            var networkManager: NetworkManager {get}
        }
  3. 添加新的构建脚本(在编译之前)

    "$PODS_ROOT/InjectGrail/Scripts/inject.sh"
  4. 添加一个实现 RootInjector 的类或结构体。这将是你最高级别的注入器,能够注入其他所有 Injectables。也可以手动创建注入器。

    struct RootInjectorImpl: RootInjector {
           let networkManager: NetworkManager
           let messagesRepository: MessagesRepository
           let authenticationManager: AuthenticationManager
    }
  5. 编译。注入脚本将在项目目录中生成文件 /Generated/Inject.generated.swift。将其添加到项目中。

  6. 对于每个需要成为 Injectable 的类,让它实现 Injectable 并通过创建字段 injectorinit(injector:...) 来满足协议要求。 实际可以使用的数据结构是由注入框架根据您的 Injector 定义创建的。例如,针对我们的 MessagesViewModel,我们创建了协议 MessagesViewModelInjector,注入框架创建了结构体 MessagesViewModelInjectorImpl(添加了 Impl)的实现。我们应该使用它。

    class MessagesViewModel: Injectable {
        let injector: MessagesViewModelInjectorImpl
        
        init(injector: MessagesViewModelInjectorImpl) {
            self.injector = injector
        }
    }

    所有来自 MessagesViewModelInjector 的属性都可以通过由 InjectGrail 自动创建的扩展直接在 MessagesViewModel 中使用。因此在这种情况下,我们可以直接使用 networkManager

    class MessagesViewModel: Injectable {
       let injector: MessagesViewModelInjectorImpl
       
       init(injector: MessagesViewModelInjectorImpl) {
           self.injector = injector
       }
       
       func doSomeAction() {
           self.networkManager.callBackend()
       }
    }
  7. 对于每个 InjectorInjectGrail 还创建一个协议 Injects<InjectorName>,在我们的例子中就是 InjectsMessagesViewModelInjector。自身是 Injectable 且希望能够注入到其他 Injectables 的类可以通过采用该协议来创建一个辅助函数 inject(...),这个函数不进行注入。InjectGrail 会自动解析当前类的 Injector 和目标 Injector 之间的依赖关系,并为 inject 函数添加所有尚未找到的参数。采用 Injects<InjectorName> 也会将目标的所有依赖项添加到当前的注入器 Impl 中。

    如果我们需要从 MessagesViewModel 创建 MessageRowViewModel,我们需要创建 MessageRowViewModelInjector 并像这样让 MessageRowViewModel 实现 Injectable

    protocol MessageRowViewModelInjector: Injector {
        var messagesRepository: MessagesRepository {get}
        var messageIndex: Int {get}
    }
    
    class MessageRowViewModel: Injectable {
       let injector: MessageRowViewModelInjectorImpl
       
       init(injector: MessageRowViewModelInjectorImpl) {
           self.injector = injector
       }
    }

    在运行注入脚本后,我们可以使 MessagesViewModel 实现 InjectsMessageRowViewModelInjector,并在脚本下一次运行后,MessagesViewModelInjectorImpl 会自动获取额外的属性 messagesRepository —— 因为它是由 RootInjector 提供的,而 MessagesViewModel 将扩展 func inject(messageIndex: Int) -> MessageRowViewModelInjector 函数,它可以用于创建 MessageRowViewModel,如下所示

    class MessagesViewModel: Injectable {
       let injector: MessagesViewModelInjectorImpl
       
       init(injector: MessagesViewModelInjectorImpl) {
           self.injector = injector
       }
       
       func createRowViewModel() {
         let rowViewModel = MessageRowViewModel(inject(messageIndex: 0))
       }
    }

    IntString 在注入过程中从不被解析。即使注入的类在其 Injector 中也有它。解析也可以通过在 Injector 中的字段添加 Sourcery 注解来手动禁用。

    protocol MessageRowViewModelInjector: Injector {
        var messagesRepository: MessagesRepository {get}
        // sourcery: forceManual
        var authenticationManager: AuthenticationManager {get}
        var messageIndex: Int {get}
    }

    在上面的例子中,authenticationManager 将始终来自注入类 inject 函数的参数。

解析逻辑

当针对父注入器解析依赖关系时,InjectGrail 通过类型定义进行搜索。如果有多个同类型的属性,则它还会通过名称进行匹配。如上所述,IntString 从不被解析。

作者

Łukasz Kwoska,[email protected]

许可协议

InjectGrail 适用于 MIT 许可证。有关更多信息,请参阅 LICENSE 文件。

致谢

  • 本项目无法成立离不开 Sourcery。它是幕后主要组件。
  • Annotation Inject - 感谢展示给我如何轻松地从 pod 使用 Sourcery。