{"id":22679820,"url":"https://github.com/provir/servicecontainerkit","last_synced_at":"2025-04-12T15:52:44.953Z","repository":{"id":62455199,"uuid":"135919356","full_name":"ProVir/ServiceContainerKit","owner":"ProVir","description":"Kit to create your own IoC Container or ServiceLocator. Use ServiceProvider as core, ServiceLocator as ready IoC Container","archived":false,"fork":false,"pushed_at":"2021-01-07T19:42:01.000Z","size":362,"stargazers_count":10,"open_issues_count":7,"forks_count":1,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-04-12T11:42:35.105Z","etag":null,"topics":["container","dependency-injection","dependency-inversion-principle","inversion-of-control","service-factories","servicelocator","serviceprovider","swift"],"latest_commit_sha":null,"homepage":"","language":"Swift","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/ProVir.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2018-06-03T16:06:25.000Z","updated_at":"2023-12-13T06:23:58.000Z","dependencies_parsed_at":"2022-11-02T00:16:46.027Z","dependency_job_id":null,"html_url":"https://github.com/ProVir/ServiceContainerKit","commit_stats":null,"previous_names":[],"tags_count":9,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ProVir%2FServiceContainerKit","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ProVir%2FServiceContainerKit/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ProVir%2FServiceContainerKit/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ProVir%2FServiceContainerKit/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ProVir","download_url":"https://codeload.github.com/ProVir/ServiceContainerKit/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248592071,"owners_count":21130172,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["container","dependency-injection","dependency-inversion-principle","inversion-of-control","service-factories","servicelocator","serviceprovider","swift"],"created_at":"2024-12-09T19:10:57.250Z","updated_at":"2025-04-12T15:52:44.925Z","avatar_url":"https://github.com/ProVir.png","language":"Swift","funding_links":[],"categories":[],"sub_categories":[],"readme":"![ServiceContainerKit](https://raw.githubusercontent.com/ProVir/ServiceContainerKit/master/ServiceContainerKitLogo.png) \n\n[![CocoaPods Compatible](https://cocoapod-badges.herokuapp.com/v/ServiceContainerKit/badge.png)](http://cocoapods.org/pods/ServiceContainerKit)\n[![Carthage Compatible](https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat)](https://github.com/ProVir/ServiceContainerKit)\n[![Platform](https://cocoapod-badges.herokuapp.com/p/ServiceContainerKit/badge.png)](http://cocoapods.org/pods/ServiceContainerKit)\n[![License](https://cocoapod-badges.herokuapp.com/l/ServiceContainerKit/badge.png)](https://github.com/ProVir/ServiceContainerKit/blob/master/LICENSE)\n\n  Kit to create your own ServiceContainer or ServiceLocator (dynamic list services). Also includes a ServiceInjects as an option. Support Objective-C in readOnly regime. \n  \n  High percentage of unit test coverage **(~ 90%)**.\n  \n  **P.S.**: We recommend that you download and study the `Example` project, which is made as one of the usage examples.\n  \n  - [Introduction](#introduction)\n  - [Features](#features)\n  - [Requirements](#requirements)\n  - [Communication](#communication)\n  - [Migration from 2.0 to 3.0](#migration-from-20-to-30)\n  - [Installation](#installation)\n  - [Usage ServiceFactory (English / Русский)](#usage-servicefactory)\n  - [Usage ServiceProvider (English / Русский)](#usage-serviceprovider)\n  - [Usage ServiceInjects (English / Русский)](#usage-serviceinjects)\n  - [Author](#author)\n  - [License](#license)\n  \n## Introduction\n  \n      *Dependency Inversion Principle (DIP from SOLI**D**)* allows you to create classes as independent as possible between each other. But developing the services using Dependency Injection, you are faced with the difficulty - how and where to set up services and communications, and how to provide these services to instances that are created during the application process, usually a presentation layer.\n\n      One way to solve this problem is to use *Dependency Injection Container* frameworks that create services for the dependencies and settings that you specify, and also if necessary, injected them in the right parts of the application. The use of such side-by-side frameworks draws certain dependencies throughout the architecture of the application and provides its functionality with certain limitations, which are discussed by the nuances of the programming language, platforms, and as a payment for their universality.\n\n      You can create your own container for a specific project, taking into account its specific features and architecture. One simple way to create your own container is to use a structure with a set of pre-configured services or their factories. Better yet, use a wrapper over services (`ServiceProvider`), which hides the way to create a service - for earlier or as needed, as well as its dependencies and the settings used. \n\n      To inject dependencies on the presentation layer, you can use 'ServiceInject`, which only requires you to make and register your container with services created according to simple defined rules.\n\n#\n\n      *Dependency Inversion Principle (DIP из SOLI**D**)* позволяет создавать классы максимально независимыми между собой. Но разрабатывая сервисы используя DIP вы сталкиваетесь с трудностью - как и где настроить сервисы и связи, а также как предоставить эти сервисы экземплярам, которые создаются в процессе работы приложения, как правило это слой представления. \n\n      Один из способов решить эту проблему - это использование фреймворков *Dependency Injection Container*, которые создают сервисы по указываемым вами зависисмостям и настройкам, а также внедряют их по необходимости в нужные части приложения. Использование подобных стороних фреймворков тянет за собой наличие определенных зависимостей во всей архитекртуре приложения и предоставляют свой функционал с определенными ограничениями, которые обсуловлены нюансами языка программирования, платформы и как плата за их универсальность.  \n\n      Вы можете создать свой собственный контейнер для конкретного проекта с учетом его специфики и архитектуры. Один из простых способов создать свой контейнер - это использовать структуру с набором созданных и настроенных заранее сервисов либо их фабрик. А еще лучше - использовать обертку над сервисами (`ServiceProvider`), скрывающую способ создания сервиса - за ранее или по необходимости, а также его зависимости и используемые настройки.\n\n      Для внедрения зависимостей на слое представления можно использовать `ServiceInject`, который только требует создать и зарегистрировать свой контейнер с сервисами, созданный по определенным простым правилам. \n\n#\n\n\n## Features\n\n`ServiceProvider` and `ServiceParamsProvider` - wrapper for the service to hide the details of its making:\n- [x] Type services: single, lazy, weak and many instance. \n- [x] Support remaked singleton services. \n- [x] Create from service factories, existing instance or closure factory. \n- [x] Throws errors when make service, result get service as optional or with detail error. \n- [x] Service factories with parameters for many instance services. \n- [x] Support thread safe providers.\n- [x] Get service from provider in Objective-C code. \n- [x] Support custom logger.\n\n\n## Requirements\n\n- iOS 10.0+ / macOS 10.12+ / tvOS 10.0+ / watchOS 3.0+\n- Xcode 11.0 and above\n- Swift 5.2 and above\n\n\n## Communication\n\n- If you **need help**, go to my telegram [@ViR_RuS](https://t.me/ViR_RuS)\n- If you **found a bug**, open an issue.\n- If you **have a feature request**, open an issue.\n- If you **want to contribute**, submit a pull request.\n\n\n## Migration from 2.0 to 3.0\n\nIn version 3.0, the `ServiceLocator` and `ServiceEasyLocator` was deleted. \nExample updated versions you can founded in `Tester` target - manually copy to your project. \n\nA lot of refactoring was done on ServiceProviders, as a result of which many types and methods were renamed. \nWe recommend that you read the documentation before migrating to the new version.\nAlso learn the new example app from target `Example`.\n\n\n## Installation\n\n### CocoaPods\n\n[CocoaPods](http://cocoapods.org) is a dependency manager for Cocoa projects. You can install it with the following command:\n\n```bash\n$ gem install cocoapods\n```\n\n\u003e CocoaPods 1.9.0+ is required to build ServiceContainerKit 3.0.0+.\n\nTo integrate ServiceContainerKit into your Xcode project using CocoaPods, specify it in your `Podfile`:\n\n```ruby\nsource 'https://github.com/CocoaPods/Specs.git'\nplatform :ios, '10.0'\n\ntarget '\u003cYour Target Name\u003e' do\n  pod 'ServiceContainerKit', '~\u003e 3.0'\n  pod 'ServiceInjects', '~\u003e 3.0'\nend\n```\n\nThen, run the following command:\n\n```bash\n$ pod install\n```\n\n\n### Carthage\n\n[Carthage](https://github.com/Carthage/Carthage) is a decentralized dependency manager that builds your dependencies and provides you with binary frameworks.\n\nYou can install Carthage with [Homebrew](http://brew.sh/) using the following command:\n\n```bash\n$ brew update\n$ brew install carthage\n```\n\nTo integrate ServiceContainerKit into your Xcode project using Carthage, specify it in your `Cartfile`:\n\n```ogdl\ngithub \"ProVir/ServiceContainerKit\" ~\u003e 3.0\n```\n\nRun `carthage update` to build the framework and drag the built `ServiceContainerKit.framework` and `ServiceInjects.framework`  into your Xcode project.\n\n\n\n### Swift Package Manager\n\nThe [Swift Package Manager](https://swift.org/package-manager/) is a tool for automating the distribution of Swift code and is integrated into the `swift` compiler. \n\nOnce you have your Swift package set up, adding ServiceContainerKit as a dependency is as easy as adding it to the `dependencies` value of your `Package.swift`.\n\n```swift\nlet package = Package(\n    dependencies: [\n        .package(url: \"https://github.com/ProVir/ServiceContainerKit\", .upToNextMajor(from: \"3.0.0\"))\n    ],\n    targets: [\n        .target(\n            dependencies: [\n                .byName(name: \"ServiceContainerKit\"), \n                .product(name: \"ServiceInjects\", package: \"ServiceContainerKit\")\n            ]\n        )\n    ]\n)\n```\n\n\n### Manually\n\nIf you prefer not to use any of the aforementioned dependency managers, you can integrate ServiceContainerKit into your project manually.\n\nCopy files from directory `ServiceContainerKit/Sources` and `ServiceInjects/Sources` in your project. \n\n\n---\n\n\n**Note:** To use the library, remember to include it in each file: `import ServiceContainerKit`.\n\nThe project has a less abstract example of using the library, which can be downloaded separately.\n\n## Usage ServiceFactory\n\n  To use `ServiceProvider` or` ServiceParamsProvider` it is recommended for each service to use a factory (struct or class) implementing the protocol `ServiceFactory`, `ServiceSessionFactory` or` ServiceParamsFactory`. \n  A factory without parameters (`ServiceFactory`) can provide a service of four types (`factoryType`):\n  - `atOne`: service in a single instance is created immediately during the creation of the ServiceProvider instance, the factory itself is no longer needed;\n  - `lazy`: service in a single instance is not created immediately, but only at the first get service. The factory exists only until the instant of creation of the service instance and is deleted after its creation;\n  - `weak`: service is not created immediately, but only at the first get service. The service exists in a single instance as long as it is used somewhere, then it is deleted and a new one will be created again when a new get request. This type is a cross between `lazy` and `many` and is usually used for performance reasons;\n  - `many`: the service is created each time a new one for each get service to receive it. It can also be used to implement its lazy initialization logic or some other - not necessarily every get service should return a new instance.\n  \n  A factory with parameters (`ServiceParamsFactory`) works only as a service of the` many` type. To implement `atOne` or` lazy` types, you need to use internal variables (the factory itself is a class) and provide them based on input parameters.\n  \n  The service creation function can return an error that will prevent the creation of the service. While get service, you can process this error. If the error was returned for a factory of the type `atOne` - then the provider will always return this error when trying to get the service. If the error was returned for a factory of the `lazy` type, the provider will attempt to create a service each time it is get service again until the service is created.\n\n#\n\n  Для использования `ServiceProvider` или `ServiceParamsProvider` рекомендуется для каждого сервиса использовать фабрику (struct или class) реализующую протокол `ServiceFactory`, `ServiceSessionFactory` или `ServiceParamsFactory`. \n  Фабрика без параметров (`ServiceFactory`) может предоставлять сервис четырех типов (`factoryType`):\n  - `atOne`: сервис в единственном экземпляре создается сразу во время создания экземпляра ServiceProvider, сама фабрика больше не нужна;\n  - `lazy`: сервис в единственном экземпляре создается не сразу, а только при первом требовании. Фабрика существует только до момента создания экземпляра сервиса и удалется после его создания;\n  - `weak`: сервис создается не сразу, а только при первом требовании. Сервис в единственном экземпляре существует пока где-либо используется, после - удаляется и при новом запросе будет создан заново новый. Этот тип среднее между `lazy` и  `many` и обычно используется ради повышения производительности;\n  - `many`: сервис создается каждый раз новый при каждом запросе на его получение. Также может использоваться для реализации своей логики lazy инициализации или какой-либо другой - не обязательно каждый запрос должен возвращать новый экземпляр.\n\n  Фабрика с параметрами (`ServiceParamsFactory`) работает только как сервис типа `many`. Для реализации типов `atOne` или `lazy` вам потребуется использовать внутренние переменные (сама фабрика при этом является классом) и предоставлять их на основе входных параметров. \n  \n  Фабрика с возможностью пересоздавать сервисы в единственном экземпляре (`ServiceSessionFactory`) работает на идеи сессий - при сменне текущей сессии на другую все зависимые сервисы пересоздаются. Вместо пересоздавания они могут деактивироваться и активироваться когда сессия станет снова активна.\n  Такой тип фабрики не поддерживает работу с `many` типом сервисов - могут быть только синглетоны. Для всех типов сервисов фабрика никогда не удаляется, для типа `atOne` сервис создается или активируется сразу при каждой смене сессии.\n\n  Функция создания сервиса может вернуть ошибку, которая предотвратит создание сервиса. Во время получения сервиса можно обработать эту ошибку. Если ошибка была возвращена для фабрики типа `atOne` - то провайдер всегда будет возвращать эту ошибку при попытки получить сервис. Если ошибка была возвращена для фабрики типа `lazy` - провайдер будет производить попытки создать сервис каждый раз при его запросе заново пока сервис не будет создан. \n\n#### An examples service factories:\n```swift\nstruct SingletonServiceFactory: ServiceFactory {\n    let mode: ServiceFactoryMode = .atOne\n    func makeService() throws -\u003e SingletonService {\n        return SingletonServiceImpl()\n    }\n}\n```\n\n```swift\nstruct LazyServiceFactory: ServiceFactory {\n    let mode: ServiceFactoryMode = .lazy\n    func makeService() throws -\u003e LazyService {\n        return LazyServiceImpl()\n    }\n}\n```\n\n```swift\nclass FirstServiceFactory: ServiceFactory {\n    let singletonServiceProvider: ServiceProvider\u003cSingletonService\u003e\n    var count: Int\n    \n    init(singletonServiceProvider: ServiceProvider\u003cSingletonService\u003e) {\n        self.singletonServiceProvider = singletonServiceProvider\n        self.count = 0\n    }\n\n    let mode: ServiceFactoryMode = .many\n    func makeService() throws -\u003e FirstService {\n        count += 1\n        defer {\n            print(\"Service created number: \\(count)\")\n        }\n        return FirstServiceImpl(singletonService: try singletonServiceProvider.getService())\n    }\n}\n```\n\n```swift\nstruct SecondServiceFactory: ServiceParamsFactory {\n    let lazyServiceProvider: ServiceProvider\u003cLazyService\u003e\n    let firstServiceProvider: ServiceProvider\u003cFirstService\u003e\n\n    func makeService(params: SecondServiceParams?) throws -\u003e SecondService {\n        let instance = SecondService(\n            lazyService: try lazyServiceProvider.getService(),\n            firstService: try firstServiceProvider.getService()\n        )\n        instance.number = params?.number ?? -1\n        return instance\n    }\n}\n```\n\n## Usage ServiceProvider\n\n### Service Container\n\n It is assumed that the Container contains service providers (`ServiceProvider` and` ServiceParamsProvider`).\n Also the container can contain important singleton services without the provider, if they are used at the start - for example, in Application Delegate or other system component. As a rule, for such services, it is better to allocate a separate container for use in these cases. An example can be found in `Example/AppDelegate.swift` and `AppDelegateServices`.\n\n  Предполагается что контейнер содержит провайдеры сервисов (`ServiceProvider` и `ServiceParamsProvider`). \n  Также контейнер может содержать важные сервисы синглетоны без провайдера, если они используются на старте - к примеру в Application Delegate или другим системном компоненте. Как правило для таких сервисов лучше выделить отдельный контейнер для использования именно в этих случаях. Пример можно посмотреть в `Example/AppDelegate.swift` и `AppDelegateServices`.\n\n#### An example Container:\n```swift\nstruct Services {\n    struct User {\n        let userService: ServiceProvider\u003cUserService\u003e\n    }\n    \n    struct Folders {\n        let manager: ServiceProvider\u003cNoteFoldersManager\u003e\n    }\n    \n    struct Notes {\n        let manager: ServiceParamsProvider\u003cNoteRecordsManager, NoteRecordsManagerParams\u003e\n        let editService: ServiceParamsProvider\u003cNoteRecordEditService, NoteRecordEditServiceParams\u003e\n    }\n    \n    let user: User\n    let folders: Folders\n    let notes: Notes\n}\n\nstruct AppDelegateServices {\n    let userService: UserService\n    let pushService: PushService\n}\n\n// MARK: Setup\nenum ServicesFactory {\n    static func makeDefault() -\u003e (Services, AppDelegateServices) {\n        let core = ServicesCore.makeDefault()\n        \n        let user = Services.User.makeDefault(core: core)\n        let folders = Services.Folders.makeDefault(core: core, user: user)\n        let notes = Services.Notes.makeDefault(core: core, user: user, folders: folders)\n        \n        let services = Services(\n            user: user,\n            folders: folders,\n            notes: notes\n        )\n        \n        let pushService = PushServiceFactory().makeService()\n        let appDelegateService = AppDelegateServices(\n            userService: user.userService.getServiceOrFatal(),\n            pushService: pushService\n        )\n        return (services, appDelegateService)\n    }\n}\n\nextension Services.Notes {\n    static func makeDefault(core: ServicesCore, user: Services.User, folders: Services.Folders) -\u003e Self {\n        let manager = NoteRecordsManagerFactory(\n            apiClient: core.apiClient,\n            userService: user.userService\n        ).serviceProvider()\n        \n        let editService = NoteRecordEditServiceFactory(\n            apiClient: core.apiClient,\n            recordsManager: manager\n        ).serviceProvider()\n        \n        return .init(manager: manager, editService: editService)\n    }\n}\n\n....\n```\n\nIn order not to depend on the library in the whole project, you can make the providers private and provide a public interface for making the service.\n\nДля того чтобы не зависеть от библиотеки во всем проекте, можно сделать провайдеры приватными и предоставить публичный интерфейс для получения самого сервиса.\n\n#### An example private ServiceProviders:\n```swift\nstruct ServiceContainer {\n    private let firstServiceProvider: ServiceProvider\u003cFirstService\u003e\n    private let secondServiceProvider: ServiceParamsProvider\u003cSecondService, SecondServiceParams?\u003e\n\n    private let userService: UserService\n\n    func getFirstService() -\u003e FirstService {\n        return firstServiceProvider.getServiceOrFatal()\n    }\n\n    func getSecondService(params: SecondServiceParams?) throws -\u003e SecondService {\n        return try secondServiceProvider.getService(params: params)\n    }\n    \n    func getUserService() -\u003e UserService {\n        return userService\n    }\n}\n```\n\n### Service[Params]Provider\n\nYou can create `ServiceProvider` in several ways:\n- using a regular factory: by calling function `ServiceFactory().serviceProvider()` (recommended) or through constructors `ServiceProvider(factory:)` and `ServiceProvider(tryFactory:)`;\n- using factory with parameters: by calling function `ServiceFactory().serviceProvider(params:)` (recommended) or through constructor `ServiceProvider(factory:params:)`;\n- sing a factory with re-maked singletons linked to sessions: by calling function `ServiceFactory().serviceProvider(mediator:)` (recommended) or through constructor `ServiceProvider(factory:mediator:)`.\n- using provider with parameters: `ServiceParamsProvider.convert(params:)`;\n- using an already created service, passing it to the constructor: `ServiceProvider()`, factory equivalent of `atOne` type;\n- using closure with mode setting: `ServiceProvider(mode:) { }`.\n\n\n\nYou can create `ServiceParamsProvider` by using a factory with parameters (`ServiceParamsFactory`): `ServiceParamsProvider(factory:)` or using closure `ServiceParamsProvider { params in }`.\n\nTo get the service it is enough to call the function `try Service[Params]Provider.getService()` which returns the service or error. \nYou can also use `Service[Params]Provider.getServiceAsResult()`,  `Service[Params]Provider.getServiceAsOptional()` - then the service is returned as an option (nil in case of an error) or `Service[Params]Provider.getServiceOrFatal()` - in case of an error, there will be a crash with detailed information about the error.\nUse `getServiceOrFatal()` instead of  `try! getService()` or `getServiceAsOptional()!`, so that the cause of the crash is not lost and is easily determined.\n\n#\n\nСоздать `ServiceProvider` можно несколькими способами:\n- используя обычную фабрику: через вызов `ServiceFactory().serviceProvider()` (рекомендуется) или через конструкторы `ServiceProvider(factory:)` и `ServiceProvider(tryFactory:)`;\n- используя фабрику с параметрами: через вызов `ServiceFactory().serviceProvider(params:)` (рекомендуется) или через конструктор `ServiceProvider(factory:params:)`;\n- используя фабрику с пересоздаваемыми сервисами синглетонами, привязанными к сессиям: через вызов `ServiceFactory().serviceProvider(mediator:)` (рекомендуется) или через конструктор `ServiceProvider(factory:mediator:)`.\n- используя провайдер с параметрами: `ServiceParamsProvider.convert(params:)`;\n- используя уже созданный сервис, передав его в конструктор: `ServiceProvider()`, эквивалент фабрики типа `atOne`;\n- используя кложур с указанием режима: `ServiceProvider(mode:) { }`.\n\nСоздать `ServiceParamsProvider` можно используя фабрику с параметрами (`ServiceParamsFactory`) `ServiceParamsProvider(factory:)` или используя кложур  `ServiceParamsProvider { params in }`.\n\nДля получения сервиса достаточно вызвать функцию `try Service[Params]Provider.getService()` которая возвращает сервис или ошибку. \nТакже можно использовать `Service[Params]Provider.getServiceAsResult()`,  `Service[Params]Provider.getServiceAsOptional()` - тогда сервис возвращается как опционал (nil в случае ошибки) или `Service[Params]Provider.getServiceOrFatal()` - в случае ошибки будет краш с подробной информацией об ошибке. \nИспользуйте `getServiceOrFatal()` вместо `try! getService()` или `getServiceAsOptional()!`, чтобы причина краша не потерялась и была легко определима.\n\n\n#### An example use ServiceProvider:\n```swift\nlet firstService = serviceContainer.firstService.getServiceOrFatal()\n\nlet secondService: SecondService\ndo {\n    secondService = try serviceContainer.firstService.getService()\n} catch let error as ServiceObtainError {\n    fatalError(error.fatalMessage)\n} catch {\n    fatalError(\"Error get firstService: \\(error)\")\n}\n```\n\n### Service[Params]SafeProvider\n\nIn some cases, you may need to get services from different threads. To support multithreading, you can use special thread-safe providers. Their task is to make each service receipt thread-safe by blocking or using synchronously a separate queue for each access to the provider and the factory.\nThis is usually not required, because the configuration of services at the start of the application and their receipt in the presentation layer occurs in the main thread. But if there are cases when the service is requested not from the main thread - you should use a secure provider. Getting the service from such a provider may be slower than usual.\n\nTo create a secure provider, use the `serviceSafeProvider()` or constructor methods.\nThe default is `NSLock`, but you can choose `DispatchSemaphore` or a separate queue `DispatchQueue`.\n\n`Service[Params]SafeProvider` is a inheritance of regular providers, so the entire standard set of methods is available and you can store and pass such a provider as a regular one. But in addition, there is one method - `getServiceAsResultNotSafe()`, which will ignore any locks and perform the usual non-secure getting of the service.\n\n#\n\nВ некоторых случаях может потребоваться получать сервисы из разных потоков. Чтобы поддержать мультипоточность можно использовать специальные потоко-безопасные провайдеры. Их задача - каждое получение сервиса сделать потоко-безопасным, блокируя или используя синхронно отдельную очередь при каждом обращении к првайдеру и фабрике.\nОбычно это не требуется, т.к. настройка сервисов при старте приложения и их получения в слое презентации происходит в главном потоке. Но если есть случаи когда сервис запрашивается не из главного потока - следует использовать безопасный провайдер. Получение сервиса у такого провайдера может быть медленнее обычного.\n\nДля создания безпасного провайдера используются методы `serviceSafeProvider()` или конструктор. \nПо умолчанию используется `NSLock`, но вы можете выбрать `DispatchSemaphore` или отдельную очередь `DispatchQueue`.\n\n`Service[Params]SafeProvider` является наследником обычных провайдеров, поэтому доступен весь стандартный набор методов и можно хранить и передавать такой провайдер как обычный. Но в дополнение есть один метод - `getServiceAsResultNotSafe()`, который проигнорирует любые блокировки и выполнит обычное не безопасное получение сервиса.\n\n\n#### An example use ServiceSafeProvider:\n```swift\nstruct ServiceContainer {\n    let firstService: ServiceProvider\u003cFirstService\u003e\n}\n\nextension ServiceContainer {\n    static func makeDefault() -\u003e ServiceContainer {\n        let firstService: ServiceSafeProvider\u003cFirstService\u003e = FirstServiceFactory().serviceSafeProvider(safeThread: .lock)\n        \n        return .init(\n            firstService: firstService\n        )\n    }\n}\n\nlet service = container.firstService.getServiceAsOptional()\n```\n\n### ServiceObtainError\n\nIf an error occurs as a result of getting the service, the provider returns `ServiceObtainError` with the original error and detailed information.\nThe error will contain information about the service in whose factory the error was throwed. Since services are dependent on each other and there is nesting when getting, the error may occur when getting a dependent service, and not when getting the original one - for this purpose, the error contains information about the path to the service with the error.\n\n#\n\nЕсли в результате получения сервиса возникла ошибка, то провайдер вернет `ServiceObtainError` с исходной ошибкой и подробной информацией.\nВ ошибке будет информацией об сервисе, в фабрике которого была получена ошибка. Т.к. сервисы зависимы между собой и при получении есть вложенность, то ошибка может возникнуть при получении зависимого сервиса, а не при запросе исходного - для этого в ошибке есть информация о пути до сервиса с ошибкой.\n\n\n#### An example get ServiceObtainError and nested services:\n```swift\nstruct FirstServiceFactory: ServiceFactory {\n    let mode: ServiceFactoryMode = .lazy\n    func makeService() throws -\u003e FirstService {\n        throw SomeError()\n    }\n}\n\nstruct SecondServiceFactory: ServiceFactory {\n    let firstService: ServiceProvider\u003cFirstService\u003e\n\n    let mode: ServiceFactoryMode = .many\n    func makeService() throws -\u003e SecondService {\n        return SecondServiceImpl(\n            firstService: try firstService.getService()\n        )\n    }\n}\n\ndo {\n    let service = try secondServiceProvider.getService()\n} catch {\n    // error is ServiceObtainError\n    // error.error is SomeError\n    \n    // error.service = FirstService\n    // error.pathServices = [SecondService, FirstService]\n    // error.isNested = true\n}\n```\n\n\n### Support Objective-C\n\nCreating and configuring the container is only available for swift code, but for objective-c, you can provide a special wrapper to getting the services.\n\n`ServiceProviderObjC` (in Objective-C is visible as `ServiceProvider`) and `ServiceParamsProviderObjC` (in Objective-C is visible as `ServiceParamsProvider`) can be created from any `Service[Params]Provider`, passing it (swift option) to the constructor in the swift code.\n\nYou can get the service through selectors:\n - `[ServiceProvider getService]`, \n - `[ServiceProvider getServiceOrFatal]`, \n - `[ServiceProvider getServiceAndReturnError:]`,\n - `[ServiceParamsProvider getServiceWithParams:]`,\n - `[ServiceParamsProvider getServiceOrFatalWithParams:]`,\n - `[ServiceParamsProvider getServiceWithParams:andReturnError:]`.\n\n\n#### An example use ServiceProvider:\n```objc\nFirstService* firstService = [serviceContainer.firstService getService];\n\nNSError* error = nil;\nSecondService* secondService = [serviceContainer.secondService getServiceAndReturnError:\u0026error];\n\nThirdService* thirdService = [serviceContainer.thirdService getServiceWithParams:@\"test\"];\n```\n\n\n## Usage ServiceInjects \n\n**Important:** To use the library, remember to include it in each file:  `import ServiceInjects`.\n\n### Introduction\n\nThe ServiceContainerKit framework assumes that the project can be divided into two layers - the presentation and service layers.\nThe presentation layer is the application screens, its visible part of the program, where each screen consists of Views, ViewControllers and their business logic.\nThe service layer is the business logic of the application itself, auxiliary entities, and everything that is used throughout the application.\nTo make services and build relationships between them, use `ServiceProvider`.\nTo provide services for the presentation layer, you can use a container, using which the `ServiceInjects` framework inject dependencies.\n\nIt is important to treat the `ServiceInjects` framework like this - this framework is only for the presentation layer, and therefore should only be used from the main thread.\nIt should not be used to create links between services - only for simple implementation of ready-made services in the screen entity.\n\nFor a more visual example, download the project and study the `Example` target.\n\n#\n\nФреймворк ServiceContainerKit предполагает что проект можно разделить на два слоя - слои презентации и сервисов. \nСлой презентации - это экраны приложения, его видимая часть программы, где каждый экран состоит из Views, ViewControllers и их бизнес логики.\nСлой сервисов - это бизнес логика самого приложения, вспомогательные сущности и все что используется во всем приложении. \nДля создания и построения связей между сервисами используется `ServiceProvider`.\nДля предоставления сервисов для слоя презентации можно использовать контейнер, используя которой уже фреймворк `ServiceInjects` внедряет зависимости.\n\nВажно относится к фреймворку `ServiceInjects` так - этот фреймворк только для слоя презентации, а значит должен использоваться только из главного потока.\nЕго не следует использовать для создания связей между сервисами - только для простого внедрения готовых сервисов в сущности экрана.\n\nДля более наглядного примера скачайте проект и изучите таргет `Example`.\n\n\n### Container and ServiceInjectResolver\n\nIn order to be able to inject services anywhere in the application, you need to make at least one container with a simple but necessary rule - its fields must be stored by service providers. Services in the container that do not follow this rule will not be inject in the application.\nThe field with the service must be of the type `ServiceProvider` or `ServiceParamsProvider`, field nesting is supported because the key is used as a KeyPath.\nThe container itself can be specified as a protocol - then it only remains to register its implementation.\nYou must register the container once before using it.\n\nYou can find out if the container is already registered - `ServiceInjectResolver.contains(Type.self)`.\nYou can also subscribe to its registration - `ServiceInjectResolver.addReadyContainerHandler(Type.self) { }`, the clojure will be called immediately if the container is already registered.\n\n#\n\nДля того чтобы была возможность внедрять сервисы в любом месте приложения, требуется создать как минимум один контейнер с простым, но необходимым правилом - его поля должны хранить првайдеры сервисов. Сервисы в контейнере, которые не следуют такому правилу внедрять в приложении не получится. \nПоле с сервисом должно быть типа `ServiceProvider` или `ServiceParamsProvider`, поддерживается вложенность полей т.к. в качестве ключа используется KeyPath.\nСам контейнер может быть указан в виде протокола - тогда остается только зарегистрировать его реализацию.\nПеред использованием необходимо зарегистрировать контейнер один раз.\n\nМожно узнать зарегистрирован ли уже контейнер - `ServiceInjectResolver.contains(Type.self)`.\nТакже можно подписаться на его регистрацию - `ServiceInjectResolver.addReadyContainerHandler(Type.self) { }`, кложур будет вызван сразу если контейнер уже зарегистрирован. \n\n\n####  Example of making and registering a container:\n```swift\nstruct Services {\n    struct Folders {\n        let manager: ServiceProvider\u003cNoteFoldersManager\u003e\n    }\n    \n    struct Notes {\n        let manager: ServiceParamsProvider\u003cNoteRecordsManager, NoteRecordsManagerParams\u003e\n        let editService: ServiceParamsProvider\u003cNoteRecordEditService, NoteRecordEditServiceParams\u003e\n    }\n    \n    let userService: ServiceProvider\u003cUserService\u003e\n    \n    let folders: Folders\n    let notes: Notes\n}\n\nenum ServicesFactory {\n    static func makeDefault() -\u003e Services {\n        let core = ServicesCore.makeDefault()\n        \n        let folders = Services.Folders.makeDefault(core: core)\n        let notes = Services.Notes.makeDefault(core: core, folders: folders)\n        \n        return Services(\n            userService: core.userService,\n            folders: folders,\n            notes: notes\n        )\n    }\n}\n\nclass AppDelegate: UIResponder, UIApplicationDelegate {\n    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -\u003e Bool {\n        let services = ServicesFactory.makeDefault()\n        ServiceInjectResolver.register(services)\n        \n        ...\n    \n        return true\n    }\n}\n\nfinal class SimpleViewController: UIViewController {\n    @ServiceInject(\\Services.userService)\n    private var userService\n\n    @ServiceInject(\\Services.folders.manager)\n    private var foldersManager\n    \n    ...\n}\n```\n\n### @ServiceInject and @ServiceParamsInject\n\nTo inject the service, you need to use `@ServiceInject` and `@ServiceParamsInject`. As a rule, you only need to know its full path - the container type and the path to the provider in it.\nDependency injection can be delayed (`lazyInject = true`), then the service will be getted only when the field is first accessed, or it will not be getted at all if it was not used.\n\nThe order of dependency inject and container registration is not important - it is only important that the container is registered before the first access to the service being injected.\nIf an object was created in which the service is injected before the container is registered, the dependency injection will actually be performed later - immediately after the container is registered (if `lazyInject = false`).\n\nIf, for some reason, the service is not injected for the first time and there is not enough information for this (no container or parameters) - there will be a crash. The line on the file in crash will indicate the place of injection of the service.\nIf during the injection of the service there is an error getting the service - it will also crash, because the `getServiceOrFatal` method is used inside.\n\n#\n\nДля внедрения сервиса нужно использовать `@ServiceInject` и `@ServiceParamsInject`. Как правило необходимо знать только его полный путь - тип контейнера и путь до провайдера в нем.\nВнедрение зависимости может быть отложенным (`lazyInject = true`), тогда сервис будет получен только при первом обращении к полю, либо не будет получен вовсе если он не был использован. \n\nПорядок внедрения зависимости и регистрация контейнера не важна - важно только чтобы контейнер был зарегистрирован до первого обращения к внедряемому сервису.\nЕсли был создан объект в котором внедряется сервис до регистрации контейнера - инъекция зависимости в реальности будет произведена позже - сразу после регистрации контейнера (если `lazyInject = false`).\n\nЕсли по каким-либо причинам при первом обращении сервис не будет внедрен и не будет достаточно информации для этого (нет контейнера или параметров) - будет краш. Строка на файл в краше при этому будет указывать на место внедрения сервиса. \nЕсли во время внедрения сервиса будет ошибка получения сервиса - тоже будет краш, т.к. внутри используется метод `getServiceOrFatal`.\n\n#### Example of the order of dependency injection and lazy injection:\n```swift\nclass UserPresenter {\n    @ServiceInject(\\Services.userService, lazyInject: true)\n    private var userService\n\n    @ServiceInject(\\Services.folders.manager)\n    private var foldersManager\n    \n    func logout() {\n        userService.logout()\n        foldersManager.refresh()\n    }\n}\n\nfunc testFirst() {\n    // ServiceInjectResolver.contains(Services.self) == false\n\n    let presenter = UserPresenter()\n    // userService not injected because not found container\n    // foldersManager not injected because not found container\n\n    let services = ServicesFactory.makeDefault()\n    ServiceInjectResolver.register(services)\n    // userService not injected because lazyInject = true\n    // foldersManager inject success\n\n    presenter.logout()\n    // userService inject success\n}\n\nfunc testSecond() {\n    let services = ServicesFactory.makeDefault()\n    ServiceInjectResolver.register(services)\n\n    let presenter = UserPresenter()\n    // userService not injected because lazyInject = true\n    // foldersManager inject success\n\n    presenter.logout()\n    // userService inject success\n}\n```\n\nTo inject a service from a provider with parameters, use `@ServiceParamsInject`. Parameters can be set immediately or later.\nParameters can only be specified once, until they are set, the service will not be injected.\n\nYou can also get the current injection status from `@ServiceInject` and `@ServiceParamsInject` and even subscribe to it.\nThe `$setReadyHandler { service in }` method will be called immediately after injection, but before use. If the service is already injected during the handler set, handler will be called immediately.\n\n#\n\nДля внедрения сервиса из провайдера с параметрами нужно использовать `@ServiceParamsInject`. Параметры можно задать сразу или позже.\nПараметры указать можно только один раз, пока они не будут указаны, сервис не будет внедрен.\n\nТакже у `@ServiceInject` и `@ServiceParamsInject` можно получить текущее состояние внедрения и даже подписаться на него.\nМетод `$setReadyHandler { service in }`  будет вызван сразу после инъекции, но до использования. Если во время установки обработчика сервис уже внедрен - он будет вызван сразу.\n\n#### Example of injecting a service with parameters:\n```swift\nstruct Dependencies {\n    @ServiceParamsInject(\\Services.firstService, params: .init(value: \"Default\")) var firstService\n    @ServiceParamsInject(\\Services.secondService, lazyInject: true) var secondService\n    \n    init(secondValue: String) {\n        $secondService.setParameters(.init(value: secondValue))\n    }\n}\n\nlet dependencies = Dependencies(secondValue: \"Custom\")\n\n// dependencies.$firstService.isReady == true\n// dependencies.$secondService.isReady == false\ndependencies.$secondService.setReadyHandler { service in\n    // Executed in the future before first use, because lazyInject = true\n}\n```\n\n### @ServiceProviderInject\n\nIn some cases, you may not need the service itself, but its source provider. For this purpose, use `@ServiceProviderInject`, passing also the path to the provider.\n\n#\n\nВ некоторых случаях может потребоваться не сам сервис, а его исходный провайдер. Для этих целей используется `@ServiceProviderInject`, передав также путь до провайдера. \n\n#### Example of injecting a provider:\n```swift\nstruct Dependencies {\n    @ServiceProviderInject(\\Services.firstService) var firstServiceProvider\n    @ServiceProviderInject(\\Services.secondService) var secondServiceProvider\n}\n\nlet dependencies = Dependencies()\nlet firstService = dependencies.firstServiceProvider.getServiceOrFatal()\nlet secondService = dependencies.secondServiceProvider.getServiceAsOptional()\n```\n\n\n### EntityInjectResolver and @EntityInject\n\nIn addition to services it is sometimes necessary to transfer from one place to another in a certain instance.\nIt is not always possible to use methods or constructors for this, for example, when creating a ViewController through a storyboard.\n\nSuch an entity can be temporarily registered in the `EntityInjectResolver`, where the entity will be stored until the first injection or until the corresponding token is deleted.\nEntities can be re-registered as many times as you want - the latest version will be used for inject.\n\nTo inject an instance for the first use, you need to register using the `EntityInjectResolver.registerForFirstInject(:autoRemoveDelay:)` method.\nThe entity will be removed automatically from `EntityInjectResolver` after the first injection, but not immediately - but in the next iteration of the main thread cycle, providing the opportunity to inject this entity in several places within the same general main thread cycle. If `autoRemoveDelay != nil` is specified, the instance will also be deleted after the specified number of seconds, if there was no single injection by that time.\n\nIf you need to manage the lifetime of an entity in `EntityInjectResolver` yourself, then use the `EntityInjectResolver.register()` method. It will return a token that needs to be stored somewhere. As soon as the token is no longer used, the entity will also be immediately deleted.\n\nFor the injection entity, you need to use `@EntityInject(Type.self)` - the original entity will be injected.\nYou can injected the value of an entity field of any nesting - `@EntityInject(\\Type.path)`.\n\n`@EntityInject` can be created before the entity being injected is registered and will be injected as soon as it is registered.\n\n#\n\nПомимо сервисов, иногда необходимо передать из одного места в другое некоторый экземпляр.\nНе всегда есть возможность для этого использовать методы или конструкторы, к примеру при создании ViewController-а через сториборд.\n\nТакой объект можно временно зарегистрировать в `EntityInjectResolver`, в котором экземпляр будет храниться до первого внедрения или пока не будет удален соотвествующий ему токен.\nЭкземпляры можно сколько угодно раз регистрировать повторно - при внедрении будет использоваться самая поздняя версия.\n\nЧтобы внедрить экземпляр до первого использования, нужно зарегистрировать используя метод `EntityInjectResolver.registerForFirstInject(:autoRemoveDelay:)`.\nЭкземпляр будет удален автоматически из `EntityInjectResolver` после первого внедрения, но не сразу - а в следующей интерации цикла главного потока, предоставляя возможность в рамках одного общего цикла главного потока внедрить этот экземпляр в нескольких местах. Если указан `autoRemoveDelay != nil`, то экземпляр также будет удален спустя указанное кол-во секунд, если к этому времени не было не единого внедрения.\n\nЕсли временем существования экземпляра в `EntityInjectResolver` нужно управлять самим, то следует использовать метод `EntityInjectResolver.register()`. Он вернет токен, который нужно где-то хранить. Как только токен перестанет использоваться, экземпляр также будет сразу удален.\n\nДля внедрения экземпляра нужно использовать `@EntityInject(Type.self)` - будет внедрен исходный экземпляр.\nМожно внедрить значение поля экземпляра любой вложенности - `@EntityInject(\\Type.path)`.\n\n`@EntityInject` может быть создан до регистрации внедряемого экземпляра и будет внедрен сразу как он будет зарегистрирован.\n\n\n#### Example of injecting a entity:\n```swift\nvar token: EntityInjectToken?\ntoken = EntityInjectResolver.register(appSettings)\n\nextension SimpleViewController {\n    /// Maked in Storyboard, perform `prepareForMake()` can be before or after make.\n    static func prepareForMake() {\n        let presenter = SimplePresenterImpl()\n        EntityInjectResolver.registerForFirstInject(presenter)\n    }\n}\n\nclass SimpleViewController: UIViewController {\n    @EntityInject(SimplePresenter.self)\n    private var presenter\n    \n    @EntityInject(\\AppSettings.common.uiConfig)\n    private var uiConfig\n    \n    ...\n}\n```\n\n### Support Objective-C\n\nIn Objective-C, KeyPath and structs are not supported, so using `@ServiceInject` and `@EntityInject` is not possible.\nBut there is support for `ServiceProviderObjC`, using which you can access services.\n\nOne of the ways to solve the problem is to use a separate container for objc code, when creating which objc providers will be injected.\nOr use a singleton container with all services at once.\n\nTo inject providers with the `ServiceProviderObjC` type, use `@ServiceProviderInject` with the objc version of the constructor.\n\n#\n\nВ Objective-C не поддерживается KeyPath и структуры, поэтому испоьзование `@ServiceInject` и `@EntityInject` невозможно.\nНо есть поддержка `ServiceProviderObjC`, используя который можно получить доступ к сервисам.\n\nОдин из вариантов как можно решить проблему - использовать отдельный контейнер для objc кода, создавая который будут внедряться провайдеры.\nЛибо использовать контейнер синглетон со всеми сервисами сразу.\n\nДля внедрения провайдеров с типом `ServiceProviderObjC` используется `@ServiceProviderInject` с objc версией конструктора.\n\n#### Example objc container:\n```swift\n@objc(Services)\nclass ServicesObjC: NSObject {\n    @objc static let shared = ServicesObjC()\n\n    @ServiceProviderInject(objc: \\Services.firstService)\n    @objc var firstService\n    \n    @ServiceProviderInject(objc: \\Services.secondService)\n    @objc var secondService\n    \n    @ServiceProviderInject(objc: \\Services.withParamsService)\n    @objc var withParamsService\n}\n```\n```objc\nFirstService* firstService = [Services.shared.firstService getService];\nWithParamsService* withParamsService = [Services.shared.withParamsService getServiceWithParams:@\"test\"];\n\nNSError* error = nil;\nSecondService* secondService = [Services.shared.secondService getServiceAndReturnError:\u0026error];\n```\n\n\n## Author\n\n[**ViR (Короткий Виталий)**](http://provir.ru)\n\n[Telegram: @ViR_RuS](https://t.me/ViR_RuS)\n\n\n## License\n\nServiceContainerKit is released under the MIT license. [See LICENSE](https://github.com/ProVir/ServiceContainerKit/blob/master/LICENSE) for details.\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fprovir%2Fservicecontainerkit","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fprovir%2Fservicecontainerkit","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fprovir%2Fservicecontainerkit/lists"}