ServiceContainerKit 3.0.0

ServiceContainerKit 3.0.0

ProVir 维护。



  • ViR (Vitaliy Korotkiy)

ServiceContainerKit

CocoaPods Compatible Carthage Compatible Platform License

用于创建自己的 ServiceContainer 或 ServiceLocator(动态服务列表)的框架。还包括可选的 ServiceInjects。支持 Objective-C 的只读模式。

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

附言。:我们建议您下载并学习 Example 项目,该项目作为使用示例之一制作。

简介

    依赖倒置原则(SOLID 中的 DIP) 允许您创建尽可能相互独立的类。但是,使用依赖注入开发服务时,您面临困难——如何在何处设置服务和通信,以及如何向在应用程序过程中创建的实例提供这些服务,通常是表示层。

    解决此问题的一种方法是通过使用 依赖注入容器 框架创建您指定的依赖项和服务,并在必要时将其注入到应用程序的正确部分。此类并排框架在应用程序架构中抽取一定的依赖关系,并以其功能提供一定的限制,这些限制由编程语言的细微之处、平台以及它们的通用性所支付。

    您可以根据特定项目的特定特性和架构创建自己的容器。创建自己容器的简单方法之一是使用一组预配置的服务或它们的工厂。更好的是,使用服务包装器(ServiceProvider),它隐藏了创建服务的方法——是在较早的时候还是按需,以及依赖性和使用的设置。

    为了在表示层注入依赖项,您可以使用 'ServiceInject',它只需要您根据简单定义的规则创建并注册您的容器和服务。

    依赖倒置原则(DIP 出自 SOLID) 允许创建尽可能相互独立的类。但在使用 DIP 开发服务时,你会遇到一个难题——如何以及在哪里配置服务及其关联,以及如何向在应用程序运行过程中创建的实例(通常是表示层)提供这些服务。

    解决这个问题的其中一种方法是使用 依赖注入容器 框架,这些框架根据你指定的依赖关系和配置创建服务,并在需要时将其注入到应用程序的相应部分。使用这些第三方框架会导致整个应用程序架构中存在特定依赖,并提供具有特定限制的功能,这些限制是由编程语言、平台特性和为了通用性所做的牺牲所决定的。

    你可以创建一个针对特定项目需求的项目 specific container,考虑其特性和架构。创建自己的容器的一个简单方法是通过预先创建和配置的服务或其工厂的结构。更好的方法是使用服务包装器(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

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

github "ProVir/ServiceContainerKit" ~> 3.0

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

Swift Package Manager

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

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

  • atOne:服务在 ServiceProvider 实例创建时立即创建,工厂本身不再需要;
  • lazy:单个实例中的服务不是立即创建的,而是在第一次获取服务时才创建。工厂存在于服务实例创建的瞬间,并在创建后删除;
  • weak:服务在第一次获取服务时才创建。只要它被用于某个地方,服务就在单个实例中存在;然后,在新获取请求时删除并再次创建一个新实例。这种类型是 lazymany 的混合,通常出于性能原因使用;
  • many:每次获取服务时都会创建一个新的服务。它也可以用于实现它的懒初始化逻辑或其他一些逻辑 - 不一定是每个获取服务都应该返回一个新实例。

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

服务创建函数可以返回一个错误,这将会防止创建服务。在获取服务时,您可以处理此错误。如果错误是因为 atOne 类型的工厂而返回的,则提供者将始终在尝试获取服务时返回此错误。如果因为 lazy 类型的工厂返回错误,则提供者在每次再次获取服务时都会尝试创建服务,直到服务创建完成。

建议为每个服务使用一个实现ServiceFactoryServiceSessionFactoryServiceParamsFactory协议的工厂(结构体或类),使用ServiceProvider或ServiceParamsProvider。不设置参数的工厂(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 使用示例

服务容器

假设容器中包含服务提供者(ServiceProviderServiceParamsProvider)。此外,容器还可以在没有提供者的情况下包含重要的单例服务,前提是在启动时使用它们 - 例如在 Application Delegate 或其他系统组件中。通常,对于这类服务,最好为这些情况分配一个单独的容器。示例可以在 Example/AppDelegate.swiftAppDelegateServices 中找到。

预想容器包含服务提供者(ServiceProviderServiceParamsProvider)。此外,容器还可以包含重要单例服务不舍提供者,前提在使用启动时它们被使用——例如在 Application Delegate 或其他系统组件中。一般情况下,对于这些服务,最好为这些情况分配一个单独的容器。这个示例可以在 Example/AppDelegate.swiftAppDelegateServices 中看到。

示例容器

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)
    }
}

....

为了避免整个项目依赖库,您可以将提供者设置为私有,并为创建服务提供公共接口。

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

示例私有 ServiceProvider

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
    }
}

Service[Params]Provider

您可以通过几种方式创建 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)创建 ServiceParamsProviderServiceParamsProvider(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,使用 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 的示例

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类型,支持字段嵌套,因为键用作KeyPath。容器本身可以作为协议指定——然后只需注册其实现。必须在使用之前将容器注册一次。

您可以通过调用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

除了服务,有时还需要在不同位置传输某个实例。例如,创建 ViewController 通过 storyboard 时,不可能总是使用方法或构造函数进行此操作。

这样的实体可以临时注册在 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代码的单独容器,在创建时将注入objc提供者。或者使用含有所有服务的单例容器。

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

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

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

要使用ServiceProviderObjC类型注入提供者,请使用与objc构造函数版本的@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(短命维塔利)

Telegram: @ViR_RuS

许可证

ServiceContainerKit以MIT许可发布。有关详细信息,请参考LICENSE