ServiceInjects 3.0.0

ServiceInjects 3.0.0

ProVir 维护。



  • 作者:
  • ViR (Vitaliy Korotkiy)

ServiceContainerKit

CocoaPods Compatible Carthage Compatible Platform License

用于创建自己的 ServiceContainer 或 ServiceLocator(动态服务列表)的工具包。还包括一个 ServiceInjects 作为选项。支持 Objective-C 在只读模式下。

单元测试覆盖率高达 (~ 90%)

备注:我们建议您下载并研究 Example 项目,该项目作为使用示例之一制作。

简介

    依赖倒置原则 (DIP 来自 SOLID) 允许您创建尽可能独立于彼此的类。但使用 DIP 开发服务时,您会遇到困难 - 如何和在哪里设置服务和通信,以及如何向在应用程序过程中创建的实例提供这些服务,这通常是表示层。

    解决此问题的一种方法是为您指定的依赖关系和设置创建服务,并且如果需要,将它们注入到应用程序的正确部分,这就需要使用 依赖注入容器 框架。使用这样的框架会在应用程序架构中保留某些依赖关系,并以其通用性为代价提供其功能,这些限制由编程语言的细微差别、平台以及它们的通用性支付。

    您可以考虑特定项目的特点和架构来创建自己的容器。创建自己的容器的简单方法之一是使用一组预先配置的服务或其工厂的结构。更好的方法是使用服务包装器( ServiceProvider),它隐藏了创建服务的方式 - 是在早期还是按需,以及其依赖关系和使用的设置。

    要在表示层注入依赖关系,您可以使用 'ServiceInject`,它只要求您按照简单的定义规则创建和注册容器与服务的实例。

    依赖倒置原则 (DIP из SOLID) позволяет создавать классы максимально независимыми между собой. Но разрабатывая сервисы использованием DIP вы сталкиваетесь с трудностью - как и где настроить сервисы и связи, а также как предоставить эти сервисы экземплярам, которые создаются в процессе работы приложения, как правило это слой представления.

    解决该问题的一种方法是通过使用依赖注入容器框架,它们根据你指定的依赖关系和设置创建服务,并在需要时将它们注入到应用程序的相应部分。使用此类第三方框架会带来整个应用程序架构中固有的特定依赖关系,并且提供具有特定局限性的功能,这些局限性由编程语言、平台的细微差别以及为了其通用性而付出的代价所导致。

    你可以根据自己的项目特性和架构创建自己的容器。创建自己的容器的简单方法之一是使用预先创建和配置好的服务集合或它们的工厂结构。更好的方法是使用服务包装器(ServiceProvider),它隐藏了服务的创建方式,无论是提前或按需,以及其依赖关系和使用的设置。

    对于在表示层注入依赖关系,可以使用ServiceInject,它只需创建和注册一个按一定简单规则创建的服务容器。

特性

ServiceProviderServiceParamsProvider - 服务包装器,隐藏其制作详情

  • 类型服务:单例、延迟、弱引用和多实例。
  • 支持重制的单例服务。
  • 从服务工厂、现有实例或闭包工厂创建。
  • 创建服务时抛出错误,结果获取可选服务或带详细错误的服务。
  • 多实例服务工厂具有参数。
  • 支持线程安全提供者。
  • 在Objective-C代码中从提供者获取服务。
  • 支持自定义日志记录器。

需求

  • iOS 10.0+ / macOS 10.12+ / tvOS 10.0+ / watchOS 3.0+
  • Xcode 11.0以上
  • Swift 5.2以上

通讯

  • 如果你需要帮助,请前往我的Telegram @ViR_RuS
  • 如果你发现了错误,请打开一个问题。
  • 如果你有一个特性请求,请打开一个问题。
  • 如果你希望贡献,请提交一个拉取请求。

从 2.0 迁移到 3.0

在 3.0 版本中,删除了 ServiceLocatorServiceEasyLocator。更新的示例版本您可以在 Tester 目标中找到——请手动将其复制到您的项目中。

对 ServiceProviders 进行了大量重构,导致许多类型和方法被重命名。建议您在迁移到新版本之前先阅读文档。同时,可以从目标 Example 中学习新的示例应用。

安装

CocoaPods

CocoaPods 是 Cocoa 项目的依赖管理器。您可以使用以下命令进行安装:

$ gem install cocoapods

要构建 ServiceContainerKit 3.0.0+,需要 CocoaPods 1.9.0+。

要使用 CocoaPods 将 ServiceContainerKit 集成到您的 Xcode 项目中,请在您的 Podfile 中指定它:

source 'https://github.com/CocoaPods/Specs.git'
platform :ios, '10.0'

target '<Your Target Name>' do
  pod 'ServiceContainerKit', '~> 3.0'
  pod 'ServiceInjects', '~> 3.0'
end

然后,运行以下命令:

$ pod install

Carthage

Carthage 是一个集中的依赖管理器,它可以构建您的依赖项并提供您二进制框架。

您可以使用以下命令通过 Homebrew 安装 Carthage:

$ brew update
$ brew install carthage

要使用 Carthage 将 ServiceContainerKit 集成到您的 Xcode 项目中,请在您的 Cartfile 中指定它:

github "ProVir/ServiceContainerKit" ~> 3.0

运行 carthage update 命令来构建框架,然后将构建的 ServiceContainerKit.frameworkServiceInjects.framework 拖放到您的 Xcode 项目中。

Swift Package Manager

Swift包管理器(Swift Package Manager)是一个用于自动化Swift代码分发工具,并且与swift编译器集成。

一旦你设置了Swift包,将ServiceContainerKit作为依赖项添加就像将它添加到Package.swift中的dependencies值一样简单。

let package = Package(
    dependencies: [
        .package(url: "https://github.com/ProVir/ServiceContainerKit", .upToNextMajor(from: "3.0.0"))
    ],
    targets: [
        .target(
            dependencies: [
                .byName(name: "ServiceContainerKit"), 
                .product(name: "ServiceInjects", package: "ServiceContainerKit")
            ]
        )
    ]
)

手动操作

如果你不希望使用上述的任何依赖项管理器,你可以手动将ServiceContainerKit集成到你的项目中。

将项目中的ServiceContainerKit/SourcesServiceInjects/Sources目录中的文件复制到项目中。


注意:要使用此库,请确保在每个文件中包含它:import ServiceContainerKit

项目包含使用此库的更少抽象的示例,可以单独下载。

ServiceFactory的使用

为了使用ServiceProvider ServiceParamsProvider,建议每个服务使用一个实现协议ServiceFactoryServiceSessionFactoryServiceParamsFactory的工厂(结构体或类)。无参数的工厂(ServiceFactory)可以提供四种类型的service(factoryType

  • atOne:在创建ServiceProvider实例时立即创建单实例的service,工厂本身不再需要;
  • lazy:单实例的service不是立即创建的,但只有在第一次获取service时才会创建。工厂仅在创建服务的实例时刻存在,并在其创建后被删除;
  • weak:service不是立即创建的,但只有在第一次获取service时才会创建。只要某处使用该service,该service将以单个实例存在,然后该实例被删除,并将在新的get请求时再次创建新的实例。此类型是lazymany之间的中间类型,通常用于性能原因;
  • many:每次获取service时都会为每个获取的服务创建新的service。它也可以用来实现其懒惰初始化逻辑或其它 — 不必然每个获取service都应该返回一个新实例。

带有参数的工厂(ServiceParamsFactory)只作为many类型的service。要实现atOnelazy类型,你需要使用内部变量(工厂本身是一个类)并根据输入参数提供它们。

创建服务的函数可以返回一个错误,这会阻止创建服务。获取服务时,你可以处理此错误。如果错误是为atOne类型的工厂返回的 — 那么提供者将在尝试获取服务时一直返回此错误。如果错误是为lazy类型的工厂返回的,提供者将每次再次尝试获取服务时都尝试创建服务,直到服务被创建。

使用 ServiceProviderServiceParamsProvider 建议每个服务使用一个结构体或类实现的工厂,该工厂实现 ServiceFactoryServiceSessionFactoryServiceParamsFactory 协议。无参数的工厂(ServiceFactory)可以提供四种类型的四..factoryType

  • atOne: 单例服务在创建 ServiceProvider 实例时立即创建,工厂不再需要;
  • lazy: 单例服务在第一次需求时创建。工厂只存在于创建服务实例之前,创建后即被删除;
  • weak: 服务在第一次需求时创建。单例服务存在直到被使用,之后会被删除,并在下一次请求时重新创建。这种类型介于 lazymany 之间,通常用于提高性能;
  • many: 服务在每次请求获取时都会创建一个新的实例。也可以用于实现自己的懒加载逻辑或其他逻辑 - 不一定每个请求都返回新的实例;

带有参数的工厂(ServiceParamsFactory)仅适用于 many 类型的服务。要实现 atOnelazy 类型,您需要使用内部变量(工厂是类),并根据输入参数提供这些变量。

可以重置服务单例的一次性创建的工厂(ServiceSessionFactory)基于会话理念 - 当更改当前会话到其他会话时,所有依赖的服务会被重新创建。这些服务可以替代重新创建而被激活和停用,当会话再次活跃时。这种类型的工厂不支持 many 类型的服务 - 只能是单例。对于所有服务类型,工厂永远不会被删除,对于 atOne 类型,服务在每次会话更替时都会被创建或激活。

创建服务的函数可能会返回错误,这会阻止服务创建。在获取服务时可以处理此错误。如果 atOne 类型工厂返回了错误 - 提供器总是在请求服务时返回此错误。如果 lazy 类型工厂返回了错误 - 提供器将在每次请求时尝试重新创建服务,直到服务被创建。

示例服务工厂

struct SingletonServiceFactory: ServiceFactory {
    let mode: ServiceFactoryMode = .atOne
    func makeService() throws -> SingletonService {
        return SingletonServiceImpl()
    }
}
struct LazyServiceFactory: ServiceFactory {
    let mode: ServiceFactoryMode = .lazy
    func makeService() throws -> LazyService {
        return LazyServiceImpl()
    }
}
class FirstServiceFactory: ServiceFactory {
    let singletonServiceProvider: ServiceProvider<SingletonService>
    var count: Int
    
    init(singletonServiceProvider: ServiceProvider<SingletonService>) {
        self.singletonServiceProvider = singletonServiceProvider
        self.count = 0
    }

    let mode: ServiceFactoryMode = .many
    func makeService() throws -> FirstService {
        count += 1
        defer {
            print("Service created number: \(count)")
        }
        return FirstServiceImpl(singletonService: try singletonServiceProvider.getService())
    }
}
struct SecondServiceFactory: ServiceParamsFactory {
    let lazyServiceProvider: ServiceProvider<LazyService>
    let firstServiceProvider: ServiceProvider<FirstService>

    func makeService(params: SecondServiceParams?) throws -> SecondService {
        let instance = SecondService(
            lazyService: try lazyServiceProvider.getService(),
            firstService: try firstServiceProvider.getService()
        )
        instance.number = params?.number ?? -1
        return instance
    }
}

ServiceProvider 使用方法

服务容器

假设容器包含服务提供者(`ServiceProvider` 和 `ServiceParamsProvider`)。此外,容器还可以包含重要的单例服务,不通过提供者进行,如果它们在启动时使用 - 例如,在 Application Delegate 或其他系统组件中。通常,对于这些服务,最好为这些情况分配单独的容器。示例可以在 Example/AppDelegate.swiftAppDelegateServices 中找到。

预想容器包含服务提供者(《ServiceProvider》和《ServiceParamsProvider》)。此外,容器还可以包含重要的单例服务,不通过提供者进行,如果它们在启动时使用——比如在Application Delegate或其他系统组件中。通常,对于这类服务,最好分出一个专门的容器来专门使用。示例可以在《Example/AppDelegate.swift》和《AppDelegateServices》中查看。

一个示例容器

struct Services {
    struct User {
        let userService: ServiceProvider<UserService>
    }
    
    struct Folders {
        let manager: ServiceProvider<NoteFoldersManager>
    }
    
    struct Notes {
        let manager: ServiceParamsProvider<NoteRecordsManager, NoteRecordsManagerParams>
        let editService: ServiceParamsProvider<NoteRecordEditService, NoteRecordEditServiceParams>
    }
    
    let user: User
    let folders: Folders
    let notes: Notes
}

struct AppDelegateServices {
    let userService: UserService
    let pushService: PushService
}

// MARK: Setup
enum ServicesFactory {
    static func makeDefault() -> (Services, AppDelegateServices) {
        let core = ServicesCore.makeDefault()
        
        let user = Services.User.makeDefault(core: core)
        let folders = Services.Folders.makeDefault(core: core, user: user)
        let notes = Services.Notes.makeDefault(core: core, user: user, folders: folders)
        
        let services = Services(
            user: user,
            folders: folders,
            notes: notes
        )
        
        let pushService = PushServiceFactory().makeService()
        let appDelegateService = AppDelegateServices(
            userService: user.userService.getServiceOrFatal(),
            pushService: pushService
        )
        return (services, appDelegateService)
    }
}

extension Services.Notes {
    static func makeDefault(core: ServicesCore, user: Services.User, folders: Services.Folders) -> Self {
        let manager = NoteRecordsManagerFactory(
            apiClient: core.apiClient,
            userService: user.userService
        ).serviceProvider()
        
        let editService = NoteRecordEditServiceFactory(
            apiClient: core.apiClient,
            recordsManager: manager
        ).serviceProvider()
        
        return .init(manager: manager, editService: editService)
    }
}

....

为了不在整个项目中依赖于库,可以将提供者设置为私有,并提供公开接口以使服务。

为了确保整个项目中不依赖于库,可以将提供者设为私有,并为其提供服务公共接口。

示例私有 ServiceProviders

struct ServiceContainer {
    private let firstServiceProvider: ServiceProvider<FirstService>
    private let secondServiceProvider: ServiceParamsProvider<SecondService, SecondServiceParams?>

    private let userService: UserService

    func getFirstService() -> FirstService {
        return firstServiceProvider.getServiceOrFatal()
    }

    func getSecondService(params: SecondServiceParams?) throws -> SecondService {
        return try secondServiceProvider.getService(params: params)
    }
    
    func getUserService() -> UserService {
        return userService
    }
}

服务[Params]提供者

您可以用几种方式创建 ServiceProvider

  • 使用常规工厂:通过调用函数 ServiceFactory().serviceProvider()(推荐)或通过构造函数 ServiceProvider(factory:)ServiceProvider(tryFactory:);
  • 使用带参数的工厂:通过调用函数 ServiceFactory().serviceProvider(params:)(推荐)或通过构造函数 ServiceProvider(factory:params:);
  • 使用带重置单例的会话链接的工厂:通过调用函数 ServiceFactory().serviceProvider(mediator:)(推荐)或通过构造函数 ServiceProvider(factory:mediator:)
  • 使用带参数的提供者:`ServiceParamsProvider.convert(params:)`;
  • 使用已创建的服务,传递给构造函数:ServiceProvider(),等同于 atOne 类型的工厂;
  • 使用带模式设置的闭包:ServiceProvider(mode:) { }

您可以通过使用参数化工厂(ServiceParamsFactory)创建 ServiceParamsProvider,使用参数化工厂(ServiceParamsProvider(factory:))或使用闭包 ServiceParamsProvider { params in }

要获取服务,只需调用函数 try Service[Params]Provider.getService(),它将返回服务或错误。您还可以使用 Service[Params]Provider.getServiceAsResult()Service[Params]Provider.getServiceAsOptional() - 这样,服务将被作为选项返回(错误时返回 nil)或 Service[Params]Provider.getServiceOrFatal() - 错误时,将发生崩溃并显示错误详情。使用 getServiceOrFatal() 而不是 try! getService()getServiceAsOptional()!,这样崩溃的原因不会丢失,并且可以轻松识别。

创建 ServiceProvider 的方法有几种

  • 使用普通工厂:通过调用 ServiceFactory().serviceProvider()(推荐)或通过构造函数 ServiceProvider(factory:)ServiceProvider(tryFactory:)
  • 使用参数化工厂:通过调用 ServiceFactory().serviceProvider(params:)(推荐)或通过构造函数 ServiceProvider(factory:params:)
  • 使用重置服务进程的工厂单例,绑定到会话:通过调用 ServiceFactory().serviceProvider(mediator:)(推荐)或通过构造函数 ServiceProvider(factory:mediator:)
  • 使用参数化提供者:ServiceParamsProvider.convert(params:)
  • 使用已创建的服务,将其传递给构造函数:ServiceProvider(),等价于 atOne 类型的工厂;
  • 使用指定模式的闭包:ServiceProvider(mode:) { }

可以通过使用参数化工厂(ServiceParamsFactory)(ServiceParamsProvider(factory:))或使用闭包(ServiceParamsProvider { params in })来创建 ServiceParamsProvider

要获取服务,只需要调用函数 try Service[Params]Provider.getService(),它将返回服务或错误。还可以使用 Service[Params]Provider.getServiceAsResult()Service[Params]Provider.getServiceAsOptional() - 这样,服务将以可选的形式返回(错误时返回 nil)或 Service[Params]Provider.getServiceOrFatal() - 错误时,将出现崩溃并显示有关错误的详细信息。建议使用 getServiceOrFatal() 而不是 try! getService()getServiceAsOptional()!,这样崩溃的原因就不容易丢失,并可以容易地确定。

ServiceProvider 的一个示例用法

let firstService = serviceContainer.firstService.getServiceOrFatal()

let secondService: SecondService
do {
    secondService = try serviceContainer.firstService.getService()
} catch let error as ServiceObtainError {
    fatalError(error.fatalMessage)
} catch {
    fatalError("Error get firstService: \(error)")
}

Service[Params]SafeProvider

在某些情况下,您可能需要从不同的线程获取服务。为了支持多线程,您可以使用特殊的线程安全提供者。他们的任务是使每次服务获取都变得线程安全,通过阻塞或对每次对提供者和工厂的访问都同步使用单独的队列。这通常不是必需的,因为服务在应用程序启动时的配置以及在表示层中的获取都是在主线程中发生的。但如果存在服务不是从主线程请求的情况,则应使用安全提供者。从这种提供者获取服务可能比通常要慢。

要创建一个安全提供者,请使用 serviceSafeProvider() 或构造函数方法。默认值是 NSLock,但您可以选择 DispatchSemaphore 或单独的队列 DispatchQueue

Service[Params]SafeProvider 是普通提供者的继承,因此可以访问完整的标准方法集,并且可以作为普通提供者存储和传递此类提供者。但除此之外,还有一个方法 - getServiceAsResultNotSafe(),它将忽略任何锁,并执行常规的非安全服务获取。

在某些情况下,可能需要从不同的线程中获取服务。为了支持多线程,可以使用特殊的线程安全提供者。他们的任务是使每次服务获取都变得线程安全,通过阻塞或对每次对提供者和工厂的访问都同步使用单独的队列。这通常不是必需的,因为服务在应用程序启动时的配置以及在表示层中的获取都是在主线程中发生的。但如果存在服务不是从主线程请求的情况,则应使用安全提供者。从这种提供者获取服务可能比通常要慢。

要创建安全提供者,使用方法 serviceSafeProvider() 或构造函数。默认值是 NSLock,但您可以选择 DispatchSemaphore 或单独的队列 DispatchQueue

Service[Params]SafeProvider 是普通提供者的继承,因此可以访问完整的标准方法集,并且可以作为普通提供者存储和传递此类提供者。但除此之外,还有一个方法 - getServiceAsResultNotSafe(),它将忽略任何锁,并执行常规的非安全服务获取。

使用 ServiceSafeProvider 的示例

struct ServiceContainer {
    let firstService: ServiceProvider<FirstService>
}

extension ServiceContainer {
    static func makeDefault() -> ServiceContainer {
        let firstService: ServiceSafeProvider<FirstService> = FirstServiceFactory().serviceSafeProvider(safeThread: .lock)
        
        return .init(
            firstService: firstService
        )
    }
}

let service = container.firstService.getServiceAsOptional()

ServiceObtainError

如果服务获取过程中发生错误,提供者将返回带有原始错误和详细信息的 ServiceObtainError。错误将包含有关错误发生的服务工厂的信息。由于服务之间相互依赖,并且在获取时存在嵌套,因此在获取依赖服务而不是原始服务时可能会发生错误 - 为了这个目的,错误包含有关错误服务的路径信息。

如果在获取服务过程中遇到错误,则服务提供程序将返回包含原始错误和详细信息的 ServiceObtainError。错误中将包含有关发生错误的工厂中的服务的详细信息。由于服务之间相互依赖,并且获取过程中存在嵌套,因此错误可能在获取依赖服务时发生,而不是在请求原始服务时,为此,错误中包含错误服务的路径信息。

获取 ServiceObtainError 和嵌套服务的示例

struct FirstServiceFactory: ServiceFactory {
    let mode: ServiceFactoryMode = .lazy
    func makeService() throws -> FirstService {
        throw SomeError()
    }
}

struct SecondServiceFactory: ServiceFactory {
    let firstService: ServiceProvider<FirstService>

    let mode: ServiceFactoryMode = .many
    func makeService() throws -> SecondService {
        return SecondServiceImpl(
            firstService: try firstService.getService()
        )
    }
}

do {
    let service = try secondServiceProvider.getService()
} catch {
    // error is ServiceObtainError
    // error.error is SomeError
    
    // error.service = FirstService
    // error.pathServices = [SecondService, FirstService]
    // error.isNested = true
}

支持 Objective-C

创建和配置容器仅适用于 Swift 代码,但对于 Objective-C,可以提供获取服务的特殊包装器。

ServiceProviderObjC(在 Objective-C 中可见为 ServiceProvider)和 ServiceParamsProviderObjC(在 Objective-C 中可见为 ServiceParamsProvider)可以由任何 Service[Params]Provider 创建,传入(Swift 选项)到 Swift 代码中的构造函数。

您可以通过选择器获取服务

  • [ServiceProvider getService],
  • [ServiceProvider getServiceOrFatal],
  • [ServiceProvider getServiceAndReturnError:],
  • [ServiceParamsProvider getServiceWithParams:],
  • [ServiceParamsProvider getServiceOrFatalWithParams:],
  • [ServiceParamsProvider getServiceWithParams:andReturnError:].

使用 ServiceProvider 的示例

FirstService* firstService = [serviceContainer.firstService getService];

NSError* error = nil;
SecondService* secondService = [serviceContainer.secondService getServiceAndReturnError:&error];

ThirdService* thirdService = [serviceContainer.thirdService getServiceWithParams:@"test"];

使用 ServiceInjects

注意:要使用此库,请务必在每个文件中包含它:import ServiceInjects

介绍

ServiceContainerKit框架假定项目可以分为两层——表示层和业务层。表示层是应用程序屏幕,其程序的可视部分,其中每个屏幕由视图、视图控制器及其业务逻辑组成。业务层是应用程序本身的业务逻辑、辅助实体以及应用程序中使用的所有内容。为了创建服务和建立它们之间的关系,请使用ServiceProvider。为了向表示层提供服务,可以使用容器,通过它,ServiceInjects框架注入依赖关系。

重要的是要像这样对待ServiceInjects框架——这个框架仅用于表示层,因此应该仅从主线程使用。它不应用于创建服务之间的连接——仅用于在屏幕实体中简单实现现成的服务。

为了有一个更直观的例子,请下载项目并研究Example目标。

ServiceContainerKit框架假定项目可以分成两层——表示层和服务层。表示层是应用程序的屏幕,它是程序的可视部分,其中每个屏幕由视图(Views)、视图控制器(ViewControllers)及其业务逻辑组成。服务层是应用程序自身的业务逻辑、辅助实体,以及应用程序中使用的所有内容。为了创建服务和建立它们之间的关系,使用ServiceProvider。为了向表示层提供服务,可以使用容器,使用这个容器,ServiceInjects框架可以注入依赖关系。

重要的对待ServiceInjects框架是这样——这个框架仅用于表示层,因此应仅从主线程使用。它不应用于创建服务之间的连接——仅用于在屏幕实体中简单实现现成的服务。

为了有一个更直观的例子,请下载项目并研究Example目标。

容器和ServiceInjectResolver

为了能够在应用程序的任何地方注入服务,您至少需要创建一个包含简单但必要的规则(其字段必须由服务提供者存储)的容器。不遵循此规则的容器中的服务将不会注入到应用程序中。必须具有服务字段的类型为ServiceProviderServiceParamsProvider,支持字段嵌套,因为键用作键路径。容器本身可以指定为协议——然后只需注册其实现即可。您必须在使用之前注册一次容器。

您可以通过ServiceInjectResolver.contains(Type.self)找出容器是否已注册。您还可以订阅其注册——ServiceInjectResolver.addReadyContainerHandler(Type.self) { },如果容器已注册,闭包将立即被调用。

为了在任何地方都能实施服务,至少需要创建一个容器,这个容器遵守简单但必要的规则——其字段需要存储服务提供者。容器内的服务若不遵循这一规则,将无法在应用中实施。服务字段应该是类型 ServiceProviderServiceParamsProvider,支持字段嵌套,因为使用了 KeyPath 作为键。容器本身可以指明为协议,这时只需要注册其实例。在使用之前,必须一次性注册该容器。

可以查询容器是否已注册——ServiceInjectResolver.contains(Type.self)。还可以订阅容器的注册——ServiceInjectResolver.addReadyContainerHandler(Type.self) { },如果容器已经注册,闭包将被立即调用。

容器制作和注册的示例

struct Services {
    struct Folders {
        let manager: ServiceProvider<NoteFoldersManager>
    }
    
    struct Notes {
        let manager: ServiceParamsProvider<NoteRecordsManager, NoteRecordsManagerParams>
        let editService: ServiceParamsProvider<NoteRecordEditService, NoteRecordEditServiceParams>
    }
    
    let userService: ServiceProvider<UserService>
    
    let folders: Folders
    let notes: Notes
}

enum ServicesFactory {
    static func makeDefault() -> Services {
        let core = ServicesCore.makeDefault()
        
        let folders = Services.Folders.makeDefault(core: core)
        let notes = Services.Notes.makeDefault(core: core, folders: folders)
        
        return Services(
            userService: core.userService,
            folders: folders,
            notes: notes
        )
    }
}

class AppDelegate: UIResponder, UIApplicationDelegate {
    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        let services = ServicesFactory.makeDefault()
        ServiceInjectResolver.register(services)
        
        ...
    
        return true
    }
}

final class SimpleViewController: UIViewController {
    @ServiceInject(\Services.userService)
    private var userService

    @ServiceInject(\Services.folders.manager)
    private var foldersManager
    
    ...
}

@ServiceInject 和 @ServiceParamsInject

为了注入服务,需要使用 @ServiceInject@ServiceParamsInject。一般情况下,只需要知道其完整路径——容器类型以及其中提供者的路径。依赖注入可以是延迟的(lazyInject = true),这样服务只能在字段首次访问时获取,如果它没有被使用,则根本不会获取。

依赖注入和容器注册的顺序并不重要——重要的是确保在第一次访问要注入的服务之前注册了容器。如果对象在容器注册之前就已经创建,并在其中注入了服务,那么实际上依赖注入将在容器注册后完成——立即在容器注册后(如果 lazyInject = false)。

如果由于某种原因,第一次注入服务时没有足够的信息(没有容器或参数)——将会崩溃。崩溃文件中的行将指示服务的注入位置。如果在服务注入期间获取服务时出错——也将崩溃,因为这使用了 getServiceOrFatal 方法。

要实施服务,需要使用 @ServiceInject@ServiceParamsInject。通常只需要知道完整路径——容器类型及其中的提供者路径。依赖注入可以是延迟的(lazyInject = true),这样只有当字段首次访问时才会获取服务,如果它没有被使用,则不会获取。

依赖注入的顺序和容器注册并不重要——唯一重要的是,在第一次访问要注入的服务之前,必须注册该容器。如果创建了对象并在注册容器之前就注入了服务,实际上依赖注入将在容器注册后完成——立即在容器注册后(如果 lazyInject = false)。

如果由于某些原因在第一次调用时服务无法部署,并且没有足够的信息(没有容器或参数)- 将会发生崩溃。崩溃时在文件中的行将指示服务部署的位置。如果在部署服务期间发生获取服务错误,也会发生崩溃,因为内部使用了`getServiceOrFatal`方法。

依赖注入和延迟注入的顺序示例

class UserPresenter {
    @ServiceInject(\Services.userService, lazyInject: true)
    private var userService

    @ServiceInject(\Services.folders.manager)
    private var foldersManager
    
    func logout() {
        userService.logout()
        foldersManager.refresh()
    }
}

func testFirst() {
    // ServiceInjectResolver.contains(Services.self) == false

    let presenter = UserPresenter()
    // userService not injected because not found container
    // foldersManager not injected because not found container

    let services = ServicesFactory.makeDefault()
    ServiceInjectResolver.register(services)
    // userService not injected because lazyInject = true
    // foldersManager inject success

    presenter.logout()
    // userService inject success
}

func testSecond() {
    let services = ServicesFactory.makeDefault()
    ServiceInjectResolver.register(services)

    let presenter = UserPresenter()
    // userService not injected because lazyInject = true
    // foldersManager inject success

    presenter.logout()
    // userService inject success
}

要从具有参数的提供者注入服务,请使用`@ServiceParamsInject`。参数可以立即设置或稍后设置。参数只能指定一次,直到设置为止,服务将不会注入。

您还可以从`@ServiceInject`和`@ServiceParamsInject`获取当前的注入状态,甚至订阅它。`$setReadyHandler { service in }`方法将在注入后立即调用,但在使用之前。如果处理程序设置期间服务已被注入,则处理程序将立即调用。

要从具有参数的提供者注入服务,请使用`@ServiceParamsInject`。参数可以立即设置或稍后设置。参数只能指定一次,直到它们被指定,服务将不会注入。

您还可以从`@ServiceInject`和`@ServiceParamsInject`获取当前注入状态,甚至可以订阅它。`$setReadyHandler { service in }`方法将在注入后立即调用,但使用之前。如果服务在设置处理程序时已注入,则直接调用处理程序。

具有参数注入服务的示例

struct Dependencies {
    @ServiceParamsInject(\Services.firstService, params: .init(value: "Default")) var firstService
    @ServiceParamsInject(\Services.secondService, lazyInject: true) var secondService
    
    init(secondValue: String) {
        $secondService.setParameters(.init(value: secondValue))
    }
}

let dependencies = Dependencies(secondValue: "Custom")

// dependencies.$firstService.isReady == true
// dependencies.$secondService.isReady == false
dependencies.$secondService.setReadyHandler { service in
    // Executed in the future before first use, because lazyInject = true
}

`@ServiceProviderInject`

在某些情况下,您可能不需要服务本身,而是它的源提供者。为此目的,请使用`@ServiceProviderInject`,并传递提供者的路径。

在某些情况下,可能需要源提供者而不是服务本身。为此,请使用`@ServiceProviderInject`,并传递到提供者的路径。

注入提供者的示例

struct Dependencies {
    @ServiceProviderInject(\Services.firstService) var firstServiceProvider
    @ServiceProviderInject(\Services.secondService) var secondServiceProvider
}

let dependencies = Dependencies()
let firstService = dependencies.firstServiceProvider.getServiceOrFatal()
let secondService = dependencies.secondServiceProvider.getServiceAsOptional()

EntityInjectResolver 和 @EntityInject

除了服务之外,有时还需要在某些实例中从一个地方转移到另一个地方。不总是可以使用方法或构造函数来做到这一点,例如,通过Storyboard创建ViewController时。

这样的实体可以临时注册到EntityInjectResolver中,直到第一次注入或相应令牌被删除之前,实体都会被存储。实体可以随意重新注册,注入时将使用最新版本。

为了第一次使用注入实例,需要使用EntityInjectResolver.registerForFirstInject(:autoRemoveDelay:)方法进行注册。实体将在第一次注入后从EntityInjectResolver中自动移除,但不是立即移除,而是等到主线程周期的下一次迭代,以便有机会在同一主线程周期内将此实体注入几个地方。如果指定了autoRemoveDelay != nil,则实体将在指定秒数后删除,如果在此期间没有进行单独的注入。

如果您需要自行管理EntityInjectResolver中实体的生命周期,请使用EntityInjectResolver.register()方法。它将返回一个需要存储的令牌。一旦令牌不再使用,实体也将立即删除。

对于注入实体,需要使用@EntityInject(Type.self) - 将注入原始实体。可以注入任意嵌套深度的实体字段值 - @EntityInject(\Type.path)

`@EntityInject`可以在注入的实体注册之前创建,并将在注册后立即注入。

除了服务之外,有时还需要将某个实例从一个地方传递到另一个地方。例如,在通过Storyboard创建ViewController时,可能没有使用方法或构造函数的这种方式。

这样的对象可以临时注册到EntityInjectResolver中,在第一次注入或相应的令牌被删除之前,实体将保存在那里。实体可以多次重新注册,最新版本将在注入时使用。

为了首次使用的注入实例,需要用EntityInjectResolver.registerForFirstInject(:autoRemoveDelay:)方法来注册。实例将在第一次注入后自动从EntityInjectResolver中移除,但不是立即移除,而是在主线程周期的下一次迭代中,使得有机会在同一主线程周期内将实例注入到多个地方。如果指定了autoRemoveDelay != nil,那么如果没有进行任何单独的注入,实例将在指定秒数后移除。

如果您需要自行管理EntityInjectResolver中实例的生命周期,则应使用EntityInjectResolver.register()方法。它将返回一个需要存储的令牌。一旦令牌不再使用,实例也将立即删除。

注入实例时,需要使用@EntityInject(Type.self) - 将注入原始实体。也可以注入任意嵌套深度的实体字段值 - @EntityInject(\Type.path)

@EntityInject可以在注入实体注册之前创建,并将在注册后立即注入。

实体注入示例

var token: EntityInjectToken?
token = EntityInjectResolver.register(appSettings)

extension SimpleViewController {
    /// Maked in Storyboard, perform `prepareForMake()` can be before or after make.
    static func prepareForMake() {
        let presenter = SimplePresenterImpl()
        EntityInjectResolver.registerForFirstInject(presenter)
    }
}

class SimpleViewController: UIViewController {
    @EntityInject(SimplePresenter.self)
    private var presenter
    
    @EntityInject(\AppSettings.common.uiConfig)
    private var uiConfig
    
    ...
}

支持Objective-C

在Objective-C中,不支持KeyPath和结构体,因此不能使用@ServiceInject@EntityInject。但是支持ServiceProviderObjC,可以使用它来访问服务。

解决该问题的一个方法是在创建时为objc代码使用单独的容器,届时将注入提供者。或者使用包含所有服务的单例容器。

要使用ServiceProviderObjC类型注入提供者,请使用与objc构造函数版本的@ServiceProviderInject

Objective-C不支持KeyPath和结构体,因此无法使用@ServiceInject@EntityInject。但支持ServiceProviderObjC,使用它可以访问服务。

解决此问题的一个方法是使用单独的容器来为Objective-C代码,创建时将注入提供者。或者使用包含所有服务的单例容器。

要注入类型为ServiceProviderObjC的提供者,请使用与Objective-C构造函数版本的@ServiceProviderInject

Objective-C容器示例

@objc(Services)
class ServicesObjC: NSObject {
    @objc static let shared = ServicesObjC()

    @ServiceProviderInject(objc: \Services.firstService)
    @objc var firstService
    
    @ServiceProviderInject(objc: \Services.secondService)
    @objc var secondService
    
    @ServiceProviderInject(objc: \Services.withParamsService)
    @objc var withParamsService
}
FirstService* firstService = [Services.shared.firstService getService];
WithParamsService* withParamsService = [Services.shared.withParamsService getServiceWithParams:@"test"];

NSError* error = nil;
SecondService* secondService = [Services.shared.secondService getServiceAndReturnError:&error];

作者

ViR (短名:Vitaliy)

Telegram: @ViR_RuS

许可

ServiceContainerKit在MIT许可下发布。详情请见LICENSE文件。查看LICENSE