From ecd199f7ccb4e42bb3094dd7e5e7650d25bd1850 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adrien=20Coye=20de=20Brune=CC=81lis?= Date: Tue, 13 Aug 2024 17:57:27 +0200 Subject: [PATCH 1/3] feat: Ground work to support Containers feat: Removed statically stored default container, now tracked by DependencyInjectionService refactor: Removed the notion of Service from the code, as a container can have many retain strategies --- README.md | 9 +++-- .../{SimpleResolver.swift => Container.swift} | 20 ++++++----- .../DependencyInjectionService.swift | 33 +++++++++++++++++++ Sources/InfomaniakDI/Factory.swift | 6 ++-- .../{InjectService.swift => Inject.swift} | 22 ++++++------- ...zyInjectService.swift => LazyInject.swift} | 30 ++++++++--------- 6 files changed, 77 insertions(+), 43 deletions(-) rename Sources/InfomaniakDI/{SimpleResolver.swift => Container.swift} (90%) create mode 100644 Sources/InfomaniakDI/DependencyInjectionService.swift rename Sources/InfomaniakDI/{InjectService.swift => Inject.swift} (73%) rename Sources/InfomaniakDI/{LazyInjectService.swift => LazyInject.swift} (73%) diff --git a/README.md b/README.md index 5ba1f80..2529500 100644 --- a/README.md +++ b/README.md @@ -21,17 +21,16 @@ Optimised to work well with SwiftUI Views. - [x] Injection by name - [x] Injection with Arguments - [x] Lazy init (with @LazyInjectService) - -## Roadmap -- [ ] Optionals -- [ ] Multiple containers +- [.] Multiple containers ## Requirements -- Swift 5.6 + +- Swift 5.7 + - SPM ## Setup +TODO: Containers doc + Early on in the lifecycle of your app, you want to write something like this : ```swift diff --git a/Sources/InfomaniakDI/SimpleResolver.swift b/Sources/InfomaniakDI/Container.swift similarity index 90% rename from Sources/InfomaniakDI/SimpleResolver.swift rename to Sources/InfomaniakDI/Container.swift index 2614f8a..9c5e306 100644 --- a/Sources/InfomaniakDI/SimpleResolver.swift +++ b/Sources/InfomaniakDI/Container.swift @@ -18,7 +18,7 @@ import Foundation /// Something minimalist that can resolve a concrete type /// /// Servicies are kept alive for the duration of the app's life -public protocol SimpleResolvable { +public protocol Resolvable { /// The main solver funtion, tries to fetch an existing object or apply a factory if availlable /// - Parameters: /// - type: the wanted type @@ -30,11 +30,11 @@ public protocol SimpleResolvable { func resolve(type: Service.Type, forCustomTypeIdentifier customIdentifier: String?, factoryParameters: [String: Any]?, - resolver: SimpleResolvable) throws -> Service + resolver: Resolvable) throws -> Service } /// Something that stores a factory for a given type -public protocol SimpleStorable { +public protocol FactoryStorable { /// Store a factory closure for a given type /// /// You will virtualy never call this directly @@ -48,7 +48,7 @@ public protocol SimpleStorable { /// A minimalist DI solution /// Once initiated, stores types as long as the app lives -public final class SimpleResolver: SimpleResolvable, SimpleStorable, CustomDebugStringConvertible { +public final class Container: Resolvable, FactoryStorable, CustomDebugStringConvertible { public var debugDescription: String { var buffer: String! queue.sync { @@ -67,11 +67,8 @@ public final class SimpleResolver: SimpleResolvable, SimpleStorable, CustomDebug case typeMissmatch(expected: String, got: String) } - /// One singleton to rule them all - public static let sharedResolver = SimpleResolver() - /// Factory collection - var factories = [String: Factoryable]() + var factories: [String: Factoryable] /// Resolved object collection var store = [String: Any]() @@ -79,6 +76,11 @@ public final class SimpleResolver: SimpleResolvable, SimpleStorable, CustomDebug /// A serial queue for thread safety private let queue = DispatchQueue(label: "com.infomaniakDI.resolver") + public init(factories: [String: any Factoryable] = [String: Factoryable]()) { + self.factories = factories + self.store = [String: Any]() + } + // MARK: SimpleStorable public func store(factory: Factoryable, @@ -96,7 +98,7 @@ public final class SimpleResolver: SimpleResolvable, SimpleStorable, CustomDebug public func resolve(type: Service.Type, forCustomTypeIdentifier customIdentifier: String?, factoryParameters: [String: Any]? = nil, - resolver: SimpleResolvable) throws -> Service { + resolver: Resolvable) throws -> Service { let serviceIdentifier = buildIdentifier(type: type, forIdentifier: customIdentifier) // load form store diff --git a/Sources/InfomaniakDI/DependencyInjectionService.swift b/Sources/InfomaniakDI/DependencyInjectionService.swift new file mode 100644 index 0000000..b4bbf0e --- /dev/null +++ b/Sources/InfomaniakDI/DependencyInjectionService.swift @@ -0,0 +1,33 @@ +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +import Foundation + +/// Entry point of the DI library +/// +/// Allows to add and remove dynamically containers. +/// Also provides a standard, shared, singleton style, container. +public final class DependencyInjectionService { + + /// Shared container of all singletons of an executable + var _sharedContainer: Container + + public var sharedContainer: Container { + _sharedContainer + } + + public init(sharedContainer: Container = Container()) { + self._sharedContainer = sharedContainer + } + +} diff --git a/Sources/InfomaniakDI/Factory.swift b/Sources/InfomaniakDI/Factory.swift index ffa1cdd..19155ea 100644 --- a/Sources/InfomaniakDI/Factory.swift +++ b/Sources/InfomaniakDI/Factory.swift @@ -14,7 +14,7 @@ import Foundation /// Something that can build a type, given some extra parameters and a resolver for chained dependency -public typealias FactoryClosure = (_ parameters: [String: Any]?, _ resolver: SimpleResolvable) throws -> Any +public typealias FactoryClosure = (_ parameters: [String: Any]?, _ resolver: Resolvable) throws -> Any /// Something that can build a type public protocol Factoryable { @@ -29,7 +29,7 @@ public protocol Factoryable { /// - factoryParameters: Extra parameters that can be used to customize a type. /// - resolver: A resolver for chained resolution /// - Returns: Return something that can be casted as the `type` declared at init. Will throw otherwise. - func build(factoryParameters: [String: Any]?, resolver: SimpleResolvable) throws -> Any + func build(factoryParameters: [String: Any]?, resolver: Resolvable) throws -> Any /// The registered type, prefer using a Protocol here. Great for testing. var type: Any.Type { get } @@ -49,7 +49,7 @@ public struct Factory: Factoryable, CustomDebugStringConvertible { } public func build(factoryParameters: [String: Any]? = nil, - resolver: SimpleResolvable = SimpleResolver.sharedResolver) throws -> Any { + resolver: Resolvable) throws -> Any { try closure(factoryParameters, resolver) } diff --git a/Sources/InfomaniakDI/InjectService.swift b/Sources/InfomaniakDI/Inject.swift similarity index 73% rename from Sources/InfomaniakDI/InjectService.swift rename to Sources/InfomaniakDI/Inject.swift index da6a4d7..8d6cec1 100644 --- a/Sources/InfomaniakDI/InjectService.swift +++ b/Sources/InfomaniakDI/Inject.swift @@ -15,25 +15,25 @@ import Foundation // MARK: - InjectService -/// A property wrapper that resolves shared objects when the host type is initialized. -@propertyWrapper public struct InjectService: CustomDebugStringConvertible, Equatable, Identifiable { +/// A property wrapper that resolves shared types when the host type is initialized. +@propertyWrapper public struct Inject: CustomDebugStringConvertible, Equatable, Identifiable { /// Identifiable /// /// Something to link the identity of this property wrapper to the underlying Service type. - public let id = ObjectIdentifier(Service.self) + public let id = ObjectIdentifier(Injected.self) /// Equatable /// - /// Two `InjectService` that points to the same `Service` Metatype are expected to be equal (for the sake of SwiftUI + /// Two `InjectService` that points to the same `Injected` Metatype are expected to be equal (for the sake of SwiftUI /// correctness) - public static func == (lhs: InjectService, rhs: InjectService) -> Bool { + public static func == (lhs: Inject, rhs: Inject) -> Bool { return lhs.id == rhs.id } public var debugDescription: String { """ <\(type(of: self)) - wrapping type:'\(Service.self)' + wrapping type:'\(Injected.self)' customTypeIdentifier:\(String(describing: customTypeIdentifier)) factoryParameters:\(String(describing: factoryParameters)) id:\(id)'> @@ -41,21 +41,21 @@ import Foundation } /// Store the resolved service - var service: Service! + var service: Injected! - public var container: SimpleResolvable + public var container: Resolvable public var customTypeIdentifier: String? public var factoryParameters: [String: Any]? public init(customTypeIdentifier: String? = nil, factoryParameters: [String: Any]? = nil, - container: SimpleResolvable = SimpleResolver.sharedResolver) { + container: Resolvable) { self.customTypeIdentifier = customTypeIdentifier self.factoryParameters = factoryParameters self.container = container do { - service = try container.resolve(type: Service.self, + service = try container.resolve(type: Injected.self, forCustomTypeIdentifier: customTypeIdentifier, factoryParameters: factoryParameters, resolver: container) @@ -64,7 +64,7 @@ import Foundation } } - public var wrappedValue: Service { + public var wrappedValue: Injected { get { service } diff --git a/Sources/InfomaniakDI/LazyInjectService.swift b/Sources/InfomaniakDI/LazyInject.swift similarity index 73% rename from Sources/InfomaniakDI/LazyInjectService.swift rename to Sources/InfomaniakDI/LazyInject.swift index 0ca695d..e9b5200 100644 --- a/Sources/InfomaniakDI/LazyInjectService.swift +++ b/Sources/InfomaniakDI/LazyInject.swift @@ -13,58 +13,58 @@ import Foundation -/// Inject a service at the first use of the property -@propertyWrapper public final class LazyInjectService: Equatable, Identifiable { +/// Inject a type at the first use of the property +@propertyWrapper public final class LazyInject: Equatable, Identifiable { /// Identifiable /// /// Something to link the identity of this property wrapper to the underlying Service type. - public let id = ObjectIdentifier(Service.self) + public let id = ObjectIdentifier(Injected.self) /// Equatable /// /// Two `LazyInjectService` that points to the same `Service` Metatype are expected to be equal (for the sake of SwiftUI /// correctness) - public static func == (lhs: LazyInjectService, rhs: LazyInjectService) -> Bool { + public static func == (lhs: LazyInject, rhs: LazyInject) -> Bool { return lhs.id == rhs.id } public var debugDescription: String { """ <\(type(of: self)) - wrapping type:'\(Service.self)' + wrapping type:'\(Injected.self)' customTypeIdentifier:\(String(describing: customTypeIdentifier)) factoryParameters:\(String(describing: factoryParameters)) id:\(id)'> """ } - /// Store the resolved service - var service: Service? + /// Store the instance of the resolved type + var resolvedInstance: Injected? - public var container: SimpleResolvable + public var container: Resolvable public var customTypeIdentifier: String? public var factoryParameters: [String: Any]? public init(customTypeIdentifier: String? = nil, factoryParameters: [String: Any]? = nil, - container: SimpleResolvable = SimpleResolver.sharedResolver) { + container: Resolvable) { self.customTypeIdentifier = customTypeIdentifier self.factoryParameters = factoryParameters self.container = container } - public var wrappedValue: Service { + public var wrappedValue: Injected { get { - if let service { - return service + if let resolvedInstance { + return resolvedInstance } do { - service = try container.resolve(type: Service.self, + resolvedInstance = try container.resolve(type: Injected.self, forCustomTypeIdentifier: customTypeIdentifier, factoryParameters: factoryParameters, resolver: container) - return service! + return resolvedInstance! } catch { fatalError("DI fatal error :\(error)") } @@ -75,7 +75,7 @@ import Foundation } /// The property wrapper itself for debugging and testing - public var projectedValue: LazyInjectService { + public var projectedValue: LazyInject { self } } From f987afb45c9e1441ea755141f9f1a1b5f20e7a1b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adrien=20Coye=20de=20Brune=CC=81lis?= Date: Tue, 13 Aug 2024 18:00:34 +0200 Subject: [PATCH 2/3] refactor: UT build and pass with the current state of the library --- Tests/InfomaniakDITests/ITInjectService.swift | 68 ++++++------- .../ITLazyInjectService.swift | 98 ++++++++++--------- .../InfomaniakDITests/ITSimpleReslover.swift | 18 ++-- Tests/InfomaniakDITests/UTHelpers.swift | 28 +++--- Tests/InfomaniakDITests/UTInjectService.swift | 54 +++++----- .../UTLazyInjectService.swift | 48 ++++----- 6 files changed, 155 insertions(+), 159 deletions(-) diff --git a/Tests/InfomaniakDITests/ITInjectService.swift b/Tests/InfomaniakDITests/ITInjectService.swift index 2703e2b..527b0e4 100644 --- a/Tests/InfomaniakDITests/ITInjectService.swift +++ b/Tests/InfomaniakDITests/ITInjectService.swift @@ -14,21 +14,24 @@ @testable import InfomaniakDI import XCTest +/// Top level shared container, used for all tests. +let someContainer = Container() + /// Integration Tests of @InjectService final class ITInjectService: XCTestCase { override func setUp() { - SimpleResolver.sharedResolver.removeAll() + someContainer.removeAll() } override func tearDown() { - SimpleResolver.sharedResolver.removeAll() + someContainer.removeAll() } // MARK: - @InjectService func testResolveSampleType_propertyWrapper() { // GIVEN - let resolver = SimpleResolver.sharedResolver + let resolver = someContainer let expectedObject = SomeClass() var factoryClosureCallCount = 0 let factory = Factory(type: SomeClass.self) { _, _ in @@ -53,7 +56,7 @@ final class ITInjectService: XCTestCase { func testResolveSampleType_propertyWrapper_protocol() { // GIVEN - let resolver = SimpleResolver.sharedResolver + let resolver = someContainer let expectedObject = SomeClassConforming() var factoryClosureCallCount = 0 let factory = Factory(type: SomeClassable.self) { _, _ in @@ -78,7 +81,7 @@ final class ITInjectService: XCTestCase { func testResolveSampleType_propertyWrapper_withCustomIdentifiers() { // GIVEN - let resolver = SimpleResolver.sharedResolver + let resolver = someContainer var factoryClosureCallCount = 0 let factory = Factory(type: SomeClass.self) { _, _ in factoryClosureCallCount += 1 @@ -109,7 +112,7 @@ final class ITInjectService: XCTestCase { func testResolveSampleType_propertyWrapper_withCustomParameters() { // GIVEN - let resolver = SimpleResolver.sharedResolver + let resolver = someContainer let expectedObject = SomeClass() let expectedFactoryParameters = ["someKey": "someValue"] var factoryClosureCallCount = 0 @@ -140,7 +143,7 @@ final class ITInjectService: XCTestCase { func testResolveSampleType_propertyWrapper_identifierAndParameters() { // GIVEN - let resolver = SimpleResolver.sharedResolver + let resolver = someContainer let expectedFactoryParameters = ["someKey": "someValue"] var factoryClosureCallCount = 0 let factory = Factory(type: SomeClass.self) { parameters, _ in @@ -177,7 +180,7 @@ final class ITInjectService: XCTestCase { func testResolveSampleType_inlineResolution() { // GIVEN - let resolver = SimpleResolver.sharedResolver + let resolver = someContainer let expectedObject = SomeClass() var factoryClosureCallCount = 0 let factory = Factory(type: SomeClass.self) { _, _ in @@ -205,43 +208,34 @@ final class ITInjectService: XCTestCase { // MARK: - Performance final class ITInjectService_Performance: XCTestCase { - override func setUp() { - SimpleResolver.sharedResolver.removeAll() - - // Make sure something is registered before doing a test. - registerAllHelperTypes() - } - - override func tearDown() { - SimpleResolver.sharedResolver.removeAll() - } /// Testing the cost of doing a massive amount of creation of a InjectService with a resolution of the wrapped type. - func testCreationAndResolutionCost() { + func testCreationAndResolutionCost(container: any Resolvable) { // WHEN measure { for _ in 0 ... 1_000_000 { - let _ = InjectService().wrappedValue + let _ = Inject(container: container).wrappedValue } } } + // TODO: Fixme /// Testing the cost of doing a massive amount of creation of InjectService, with comparison to mimic SwiftUI behaviour. - func testCreationAndComparisonCost() { - // GIVEN - @InjectService var baseProperty: SomeClass - let _ = $baseProperty.wrappedValue // force a resolution - - // WHEN - measure { - for _ in 0 ... 1_000_000 { - @InjectService var newProperty: SomeClass - guard $baseProperty != $newProperty else { - return - } - - XCTFail("Unexpected") - } - } - } +// func testCreationAndComparisonCost(container: any Resolvable) { +// // GIVEN +// @InjectService(container: container) var baseProperty: SomeClass +// let _ = $baseProperty.wrappedValue // force a resolution +// +// // WHEN +// measure { +// for _ in 0 ... 1_000_000 { +// @InjectService(container: container) var newProperty: SomeClass +// guard $baseProperty != $newProperty else { +// return +// } +// +// XCTFail("Unexpected") +// } +// } +// } } diff --git a/Tests/InfomaniakDITests/ITLazyInjectService.swift b/Tests/InfomaniakDITests/ITLazyInjectService.swift index 7ef4d8e..128283b 100644 --- a/Tests/InfomaniakDITests/ITLazyInjectService.swift +++ b/Tests/InfomaniakDITests/ITLazyInjectService.swift @@ -14,21 +14,21 @@ @testable import InfomaniakDI import XCTest -/// Integration Tests of `LazyInjectService` -final class ITLazyInjectService: XCTestCase { +/// Integration Tests of `LazyInject` +final class ITLazyInject: XCTestCase { override func setUp() { - SimpleResolver.sharedResolver.removeAll() + someContainer.removeAll() } override func tearDown() { - SimpleResolver.sharedResolver.removeAll() + someContainer.removeAll() } - // MARK: - @LazyInjectService + // MARK: - @LazyInject func testResolveSampleType_propertyWrapper() { // GIVEN - let resolver = SimpleResolver.sharedResolver + let resolver = someContainer let expectedObject = SomeClass() var factoryClosureCallCount = 0 let factory = Factory(type: SomeClass.self) { _, _ in @@ -40,7 +40,7 @@ final class ITLazyInjectService: XCTestCase { // WHEN let classWithDIProperty = ClassThatUsesLazyDI() - XCTAssertNil(classWithDIProperty.$injected.service, "the service is not expected to be resolved yet") + XCTAssertNil(classWithDIProperty.$injected.resolvedInstance, "the type is not expected to be resolved yet") // THEN XCTAssertTrue(expectedObject === classWithDIProperty.injected, "identity of resolved object should match") @@ -50,12 +50,12 @@ final class ITLazyInjectService: XCTestCase { XCTAssertEqual(resolver.factories.count, 1) XCTAssertEqual(resolver.store.count, 1) - XCTAssertNotNil(classWithDIProperty.$injected.service, "the service should be resolved") + XCTAssertNotNil(classWithDIProperty.$injected.resolvedInstance, "the type should be resolved") } func testResolveSampleType_propertyWrapper_protocol() { // GIVEN - let resolver = SimpleResolver.sharedResolver + let resolver = someContainer let expectedObject = SomeClassConforming() var factoryClosureCallCount = 0 let factory = Factory(type: SomeClassable.self) { _, _ in @@ -67,7 +67,7 @@ final class ITLazyInjectService: XCTestCase { // WHEN let classWithDIProperty = ClassThatUsesLazyConformingDI() - XCTAssertNil(classWithDIProperty.$injected.service, "the service is not expected to be resolved yet") + XCTAssertNil(classWithDIProperty.$injected.resolvedInstance, "the type is not expected to be resolved yet") // THEN XCTAssertTrue(expectedObject === classWithDIProperty.injected, "identity of resolved object should match") @@ -76,12 +76,12 @@ final class ITLazyInjectService: XCTestCase { XCTAssertEqual(resolver.factories.count, resolver.store.count) XCTAssertEqual(resolver.factories.count, 1) XCTAssertEqual(resolver.store.count, 1) - XCTAssertNotNil(classWithDIProperty.$injected.service, "the service is expected to be resolved") + XCTAssertNotNil(classWithDIProperty.$injected.resolvedInstance, "the type is expected to be resolved") } func testResolveSampleType_propertyWrapper_withCustomIdentifiers() { // GIVEN - let resolver = SimpleResolver.sharedResolver + let resolver = someContainer var factoryClosureCallCount = 0 let factory = Factory(type: SomeClass.self) { _, _ in factoryClosureCallCount += 1 @@ -97,8 +97,8 @@ final class ITLazyInjectService: XCTestCase { // WHEN let classWithServicies = ClassThatUsesLazyCustomIdentifiersDI() - XCTAssertNil(classWithServicies.$special.service, "the service is not expected to be resolved yet") - XCTAssertNil(classWithServicies.$custom.service, "the service is not expected to be resolved yet") + XCTAssertNil(classWithServicies.$special.resolvedInstance, "the type is not expected to be resolved yet") + XCTAssertNil(classWithServicies.$custom.resolvedInstance, "the type is not expected to be resolved yet") // THEN XCTAssertFalse(classWithServicies.custom === classWithServicies.special, @@ -109,13 +109,13 @@ final class ITLazyInjectService: XCTestCase { XCTAssertEqual(resolver.factories.count, 2) XCTAssertEqual(resolver.store.count, 2) - XCTAssertNotNil(classWithServicies.$special.service, "the service is expected to be resolved") - XCTAssertNotNil(classWithServicies.$custom.service, "the service is expected to be resolved") + XCTAssertNotNil(classWithServicies.$special.resolvedInstance, "the type is expected to be resolved") + XCTAssertNotNil(classWithServicies.$custom.resolvedInstance, "the type is expected to be resolved") } func testResolveSampleType_propertyWrapper_withCustomParameters() { // GIVEN - let resolver = SimpleResolver.sharedResolver + let resolver = someContainer let expectedObject = SomeClass() let expectedFactoryParameters = ["someKey": "someValue"] var factoryClosureCallCount = 0 @@ -132,22 +132,22 @@ final class ITLazyInjectService: XCTestCase { resolver.store(factory: factory) // WHEN - let classWithService = ClassThatUsesLazyFactoryParametersDI() - XCTAssertNil(classWithService.$injected.service, "the service is not expected to be resolved yet") + let classWith = ClassThatUsesLazyFactoryParametersDI() + XCTAssertNil(classWith.$injected.resolvedInstance, "the type is not expected to be resolved yet") // THEN - XCTAssertTrue(classWithService.injected === expectedObject, "the identity is expected to match") + XCTAssertTrue(classWith.injected === expectedObject, "the identity is expected to match") XCTAssertEqual(factoryClosureCallCount, 1, "the factory closure should be called twice exactly") XCTAssertEqual(resolver.factories.count, resolver.store.count) XCTAssertEqual(resolver.factories.count, 1) XCTAssertEqual(resolver.store.count, 1) - XCTAssertNotNil(classWithService.$injected.service, "the service is expected to be resolved") + XCTAssertNotNil(classWith.$injected.resolvedInstance, "the type is expected to be resolved") } func testResolveSampleType_propertyWrapper_identifierAndParameters() { // GIVEN - let resolver = SimpleResolver.sharedResolver + let resolver = someContainer let expectedFactoryParameters = ["someKey": "someValue"] var factoryClosureCallCount = 0 let factory = Factory(type: SomeClass.self) { parameters, _ in @@ -169,8 +169,8 @@ final class ITLazyInjectService: XCTestCase { // WHEN let classWithServicies = ClassThatUsesLazyComplexDI() - XCTAssertNil(classWithServicies.$special.service, "the service is not expected to be resolved yet") - XCTAssertNil(classWithServicies.$custom.service, "the service is not expected to be resolved yet") + XCTAssertNil(classWithServicies.$special.resolvedInstance, "the type is not expected to be resolved yet") + XCTAssertNil(classWithServicies.$custom.resolvedInstance, "the type is not expected to be resolved yet") // THEN XCTAssertFalse(classWithServicies.custom === classWithServicies.special, @@ -181,14 +181,14 @@ final class ITLazyInjectService: XCTestCase { XCTAssertEqual(resolver.factories.count, 2) XCTAssertEqual(resolver.store.count, 2) - XCTAssertNotNil(classWithServicies.$special.service, "the service is expected to be resolved") - XCTAssertNotNil(classWithServicies.$custom.service, "the service is expected to be resolved") + XCTAssertNotNil(classWithServicies.$special.resolvedInstance, "the type is expected to be resolved") + XCTAssertNotNil(classWithServicies.$custom.resolvedInstance, "the type is expected to be resolved") } // You should not do inline lazy DI, but still I test it. func testResolveSampleType_inlineResolution() { // GIVEN - let resolver = SimpleResolver.sharedResolver + let resolver = someContainer let expectedObject = SomeClass() var factoryClosureCallCount = 0 let factory = Factory(type: SomeClass.self) { _, _ in @@ -215,38 +215,38 @@ final class ITLazyInjectService: XCTestCase { // MARK: - Performance -final class ITLazyInjectService_Performance: XCTestCase { +final class ITLazyInject_Performance: XCTestCase { override func setUp() { - SimpleResolver.sharedResolver.removeAll() + someContainer.removeAll() // Make sure something is registered before doing a test. - registerAllHelperTypes() + registerAllHelperTypes(container: someContainer) } override func tearDown() { - SimpleResolver.sharedResolver.removeAll() + someContainer.removeAll() } - /// Testing the cost of doing a massive amount of creation of a LazyInjectService with a resolution of the wrapped type. + /// Testing the cost of doing a massive amount of creation of a LazyInject with a resolution of the wrapped type. func testCreationAndResolutionCost() { // WHEN measure { for _ in 0 ... 1_000_000 { - let _ = LazyInjectService().wrappedValue + let _ = LazyInject(container: someContainer).wrappedValue } } } - /// Testing the cost of doing a massive amount of creation of LazyInjectService, with comparison to mimic SwiftUI behaviour. + /// Testing the cost of doing a massive amount of creation of LazyInject, with comparison to mimic SwiftUI behaviour. func testCreationAndComparisonCost() { // GIVEN - @LazyInjectService var baseProperty: SomeClass + @LazyInject(container: someContainer) var baseProperty: SomeClass let _ = $baseProperty.wrappedValue // force a resolution to mimic SwiftUI, should not have an impact. // WHEN measure { for _ in 0 ... 1_000_000 { - @LazyInjectService var newProperty: SomeClass + @LazyInject(container: someContainer) var newProperty: SomeClass guard $baseProperty != $newProperty else { return } @@ -263,41 +263,43 @@ final class ITLazyInjectService_Performance: XCTestCase { class ClassThatUsesLazyDI { init() {} - @LazyInjectService var injected: SomeClass + @LazyInject(container: someContainer) var injected: SomeClass } /// A class with only one resolved property class ClassThatUsesLazyConformingDI { init() {} - @LazyInjectService var injected: SomeClassable + @LazyInject(container: someContainer) var injected: SomeClassable } /// A class with one resolved property using `factoryParameters` class ClassThatUsesLazyFactoryParametersDI { init() {} - @LazyInjectService(factoryParameters: ["someKey": "someValue"]) var injected: SomeClass + @LazyInject(factoryParameters: ["someKey": "someValue"], container: someContainer) var injected: SomeClass } /// A class with two resolved properties of the same type using `customTypeIdentifier` class ClassThatUsesLazyCustomIdentifiersDI { init() {} - @LazyInjectService(customTypeIdentifier: "specialIdentifier") var special: SomeClass + @LazyInject(customTypeIdentifier: "specialIdentifier", container: someContainer) var special: SomeClass - @LazyInjectService(customTypeIdentifier: "customIdentifier") var custom: SomeClass + @LazyInject(customTypeIdentifier: "customIdentifier", container: someContainer) var custom: SomeClass } /// A class with two resolved properties of the same type using `customTypeIdentifier` and using `factoryParameters` class ClassThatUsesLazyComplexDI { init() {} - @LazyInjectService(customTypeIdentifier: "special", - factoryParameters: ["someKey": "someValue"]) var special: SomeClass + @LazyInject(customTypeIdentifier: "special", + factoryParameters: ["someKey": "someValue"], + container: someContainer) var special: SomeClass - @LazyInjectService(customTypeIdentifier: "custom", - factoryParameters: ["someKey": "someValue"]) var custom: SomeClass + @LazyInject(customTypeIdentifier: "custom", + factoryParameters: ["someKey": "someValue"], + container: someContainer) var custom: SomeClass } /// Resolve a type inside a function @@ -305,7 +307,7 @@ class ClassThatDoesLazyInlineDI { init() {} func inlineDI() -> SomeClass { - let resolved = LazyInjectService().wrappedValue + let resolved = LazyInject(container: someContainer).wrappedValue return resolved } } @@ -314,10 +316,10 @@ class ClassThatDoesLazyInlineDI { class ClassThatChainsLazyDI { init() {} - @LazyInjectService var injected: ClassWithSomeDependentType + @LazyInject(container: someContainer) var injected: ClassWithSomeDependentType } /// A class with only one resolved property struct StructThatChainsLazyDI { - @LazyInjectService var injected: StructWithSomeDependentType + @LazyInject(container: someContainer) var injected: StructWithSomeDependentType } diff --git a/Tests/InfomaniakDITests/ITSimpleReslover.swift b/Tests/InfomaniakDITests/ITSimpleReslover.swift index 6586168..e8f6b07 100644 --- a/Tests/InfomaniakDITests/ITSimpleReslover.swift +++ b/Tests/InfomaniakDITests/ITSimpleReslover.swift @@ -17,18 +17,18 @@ import XCTest /// Integration Tests of the Simple DI mechanism final class ITSimpleReslover: XCTestCase { override func setUp() { - SimpleResolver.sharedResolver.removeAll() + someContainer.removeAll() } override func tearDown() { - SimpleResolver.sharedResolver.removeAll() + someContainer.removeAll() } // MARK: - store(factory:) func testStoreFactory_mainThread() { // GIVEN - let resolver = SimpleResolver.sharedResolver + let resolver = someContainer let expectedObject = SomeClass() var factoryClosureCallCount = 0 let factory = Factory(type: SomeClass.self) { _, _ in @@ -38,7 +38,7 @@ final class ITSimpleReslover: XCTestCase { // WHEN resolver.store(factory: factory) - let result = InjectService().wrappedValue + let result = Inject(container: someContainer).wrappedValue // THEN XCTAssertNotNil(result) @@ -48,7 +48,7 @@ final class ITSimpleReslover: XCTestCase { func testStoreFactory_other() { // GIVEN - let resolver = SimpleResolver.sharedResolver + let resolver = someContainer let expectedObject = SomeClass() var factoryClosureCallCount = 0 let factory = Factory(type: SomeClass.self) { _, _ in @@ -63,7 +63,7 @@ final class ITSimpleReslover: XCTestCase { DispatchQueue.global(qos: .userInitiated).async { resolver.store(factory: factory) - let result = InjectService().wrappedValue + let result = Inject(container: someContainer).wrappedValue XCTAssertNotNil(result) @@ -82,7 +82,7 @@ final class ITSimpleReslover: XCTestCase { func testResolveSampleType_callExplicitResolve() { // GIVEN - let resolver = SimpleResolver.sharedResolver + let resolver = someContainer let expectedObject = SomeClass() var factoryClosureCallCount = 0 let factory = Factory(type: SomeClass.self) { _, _ in @@ -112,7 +112,7 @@ final class ITSimpleReslover: XCTestCase { func testResolveSampleType_chainedDependency_classes() { // GIVEN - let resolver = SimpleResolver.sharedResolver + let resolver = someContainer let expectedObject = SomeClass() var factoryClosureCallCount = 0 let factory = Factory(type: SomeClass.self) { _, _ in @@ -158,7 +158,7 @@ final class ITSimpleReslover: XCTestCase { func testResolveSampleType_chainedDependency_struct() { // GIVEN - let resolver = SimpleResolver.sharedResolver + let resolver = someContainer let expectedStruct = SomeStruct() var factoryClosureCallCount = 0 let factory = Factory(type: SomeStruct.self) { _, _ in diff --git a/Tests/InfomaniakDITests/UTHelpers.swift b/Tests/InfomaniakDITests/UTHelpers.swift index de60e8a..b4742e6 100644 --- a/Tests/InfomaniakDITests/UTHelpers.swift +++ b/Tests/InfomaniakDITests/UTHelpers.swift @@ -14,8 +14,8 @@ import Foundation @testable import InfomaniakDI -public func registerAllHelperTypes() { - let resolver = SimpleResolver.sharedResolver +public func registerAllHelperTypes(container: Container) { + let resolver = container let factories = [ Factory(type: SomeClass.self) { _, _ in @@ -57,48 +57,48 @@ class SomeClassConforming: SomeClassable {} class ClassThatUsesDI { init() {} - @InjectService var injected: SomeClass + @Inject(container: someContainer) var injected: SomeClass } /// A class with only one resolved property class ClassThatUsesConformingDI { init() {} - @InjectService var injected: SomeClassable + @Inject(container: someContainer) var injected: SomeClassable } /// A class with one resolved property using `factoryParameters` class ClassThatUsesFactoryParametersDI { init() {} - @InjectService(factoryParameters: ["someKey": "someValue"]) var injected: SomeClass + @Inject(factoryParameters: ["someKey": "someValue"], container: someContainer) var injected: SomeClass } /// A class with two resolved properties of the same type using `customTypeIdentifier` class ClassThatUsesCustomIdentifiersDI { init() {} - @InjectService(customTypeIdentifier: "specialIdentifier") var special: SomeClass + @Inject(customTypeIdentifier: "specialIdentifier", container: someContainer) var special: SomeClass - @InjectService(customTypeIdentifier: "customIdentifier") var custom: SomeClass + @Inject(customTypeIdentifier: "customIdentifier", container: someContainer) var custom: SomeClass } /// A class with two resolved properties of the same type using `customTypeIdentifier` and using `factoryParameters` class ClassThatUsesComplexDI { init() {} - @InjectService(customTypeIdentifier: "special", - factoryParameters: ["someKey": "someValue"]) var special: SomeClass + @Inject(customTypeIdentifier: "special", + factoryParameters: ["someKey": "someValue"], container: someContainer) var special: SomeClass - @InjectService(customTypeIdentifier: "custom", - factoryParameters: ["someKey": "someValue"]) var custom: SomeClass + @Inject(customTypeIdentifier: "custom", + factoryParameters: ["someKey": "someValue"], container: someContainer) var custom: SomeClass } /// A class with only one resolved property class ClassThatChainsDI { init() {} - @InjectService var injected: ClassWithSomeDependentType + @Inject(container: someContainer) var injected: ClassWithSomeDependentType } /// Resolve a type inside a function @@ -106,7 +106,7 @@ class ClassThatDoesInlineDI { init() {} func inlineDI() -> SomeClass { - let resolved = InjectService().wrappedValue + let resolved = Inject(container: someContainer).wrappedValue return resolved } } @@ -127,7 +127,7 @@ struct SomeStruct { /// A class with only one resolved property struct StructThatChainsDI { - @InjectService var injected: StructWithSomeDependentType + @Inject(container: someContainer) var injected: StructWithSomeDependentType } class StructWithSomeDependentType { diff --git a/Tests/InfomaniakDITests/UTInjectService.swift b/Tests/InfomaniakDITests/UTInjectService.swift index 31b662b..e1b954a 100644 --- a/Tests/InfomaniakDITests/UTInjectService.swift +++ b/Tests/InfomaniakDITests/UTInjectService.swift @@ -14,24 +14,24 @@ @testable import InfomaniakDI import XCTest -/// Unit Tests of `InjectService` regarding identity and equality -final class UTInjectService_Identity: XCTestCase { +/// Unit Tests of `Inject` regarding identity and equality +final class UTInject_Identity: XCTestCase { override func setUp() { - SimpleResolver.sharedResolver.removeAll() + someContainer.removeAll() // Make sure something is registered before doing a test, resolution does not matter in this test set. - registerAllHelperTypes() + registerAllHelperTypes(container: someContainer) } override func tearDown() { - SimpleResolver.sharedResolver.removeAll() + someContainer.removeAll() } - /// Two `InjectService` that points to the same Metatype are expected to be equal (for the sake of SwiftUI correctness) - func testTwoLazyInjectService_SameIdentityOfPropertyWrappers_ForClass() { + /// Two `Inject` that points to the same Metatype are expected to be equal (for the sake of SwiftUI correctness) + func testTwoLazyInject_SameIdentityOfPropertyWrappers_ForClass() { // GIVEN - @InjectService var some: SomeClass - @InjectService var someBis: SomeClass + @Inject(container: someContainer) var some: SomeClass + @Inject(container: someContainer) var someBis: SomeClass // THEN XCTAssertEqual($some, $someBis, "Equality of the property wrappers are expected to match") @@ -41,10 +41,10 @@ final class UTInjectService_Identity: XCTestCase { ) } - func testTwoLazyInjectService_SameIdentityOfPropertyWrappers_ForProtocol() { + func testTwoLazyInject_SameIdentityOfPropertyWrappers_ForProtocol() { // GIVEN - @InjectService var some: SomeClassable - @InjectService var someBis: SomeClassable + @Inject(container: someContainer) var some: SomeClassable + @Inject(container: someContainer) var someBis: SomeClassable // THEN XCTAssertEqual($some, $someBis, "Equality of the property wrappers are expected to match") @@ -54,10 +54,10 @@ final class UTInjectService_Identity: XCTestCase { ) } - func testTwoLazyInjectService_SameIdentityOfPropertyWrappers_ForStruct() { + func testTwoLazyInject_SameIdentityOfPropertyWrappers_ForStruct() { // GIVEN - @InjectService var some: SomeStruct - @InjectService var someBis: SomeStruct + @Inject(container: someContainer) var some: SomeStruct + @Inject(container: someContainer) var someBis: SomeStruct // THEN XCTAssertEqual($some, $someBis, "Equality of the property wrappers are expected to match") @@ -67,10 +67,10 @@ final class UTInjectService_Identity: XCTestCase { ) } - func testTwoLazyInjectService_SameIdentityOfPropertyWrappers_ForEnum() { + func testTwoLazyInject_SameIdentityOfPropertyWrappers_ForEnum() { // GIVEN - @InjectService var some: SomeEnum - @InjectService var someBis: SomeEnum + @Inject(container: someContainer) var some: SomeEnum + @Inject(container: someContainer) var someBis: SomeEnum // THEN XCTAssertEqual($some, $someBis, "Equality of the property wrappers are expected to match") @@ -80,10 +80,10 @@ final class UTInjectService_Identity: XCTestCase { ) } - func testTwoLazyInjectService_NotSameIdentityOfPropertyWrappers_BetweenTwoClasses() { + func testTwoLazyInject_NotSameIdentityOfPropertyWrappers_BetweenTwoClasses() { // GIVEN - @InjectService var some: SomeClass - @InjectService var someOther: SomeOtherClass + @Inject(container: someContainer) var some: SomeClass + @Inject(container: someContainer) var someOther: SomeOtherClass // THEN XCTAssertFalse( @@ -92,10 +92,10 @@ final class UTInjectService_Identity: XCTestCase { ) } - func testTwoLazyInjectService_NotSameIdentityOfPropertyWrappers_BetweenClassAndProtocol() { + func testTwoLazyInject_NotSameIdentityOfPropertyWrappers_BetweenClassAndProtocol() { // GIVEN - @InjectService var someClass: SomeClass - @InjectService var someProtocol: SomeClassable + @Inject(container: someContainer) var someClass: SomeClass + @Inject(container: someContainer) var someProtocol: SomeClassable // THEN XCTAssertFalse( @@ -104,10 +104,10 @@ final class UTInjectService_Identity: XCTestCase { ) } - func testTwoLazyInjectService_SameIdentityOfPropertyWrappers_BetweenProtocolAndConformingClass() { + func testTwoLazyInject_SameIdentityOfPropertyWrappers_BetweenProtocolAndConformingClass() { // GIVEN - @InjectService var someClassConforming: SomeClassConforming - @InjectService var someProtocol: SomeClassable + @Inject(container: someContainer) var someClassConforming: SomeClassConforming + @Inject(container: someContainer) var someProtocol: SomeClassable // THEN XCTAssertFalse( diff --git a/Tests/InfomaniakDITests/UTLazyInjectService.swift b/Tests/InfomaniakDITests/UTLazyInjectService.swift index d9564f2..8c8db60 100644 --- a/Tests/InfomaniakDITests/UTLazyInjectService.swift +++ b/Tests/InfomaniakDITests/UTLazyInjectService.swift @@ -14,13 +14,13 @@ @testable import InfomaniakDI import XCTest -/// Unit Tests of `LazyInjectService` regarding identity and equality -final class UTLazyInjectService_Identity: XCTestCase { - /// Two `LazyInjectService` that points to the same Metatype are expected to be equal (for the sake of SwiftUI correctness) - func testTwoLazyInjectService_SameIdentityOfPropertyWrappers_ForClass() { +/// Unit Tests of `LazyInject` regarding identity and equality +final class UTLazyInject_Identity: XCTestCase { + /// Two `LazyInject` that points to the same Metatype are expected to be equal (for the sake of SwiftUI correctness) + func testTwoLazyInject_SameIdentityOfPropertyWrappers_ForClass() { // GIVEN - @LazyInjectService var some: SomeClass - @LazyInjectService var someBis: SomeClass + @LazyInject(container: someContainer) var some: SomeClass + @LazyInject(container: someContainer) var someBis: SomeClass // THEN XCTAssertEqual($some, $someBis, "Equality of the property wrappers are expected to match") @@ -30,10 +30,10 @@ final class UTLazyInjectService_Identity: XCTestCase { ) } - func testTwoLazyInjectService_SameIdentityOfPropertyWrappers_ForProtocol() { + func testTwoLazyInject_SameIdentityOfPropertyWrappers_ForProtocol() { // GIVEN - @LazyInjectService var some: SomeClassable - @LazyInjectService var someBis: SomeClassable + @LazyInject(container: someContainer) var some: SomeClassable + @LazyInject(container: someContainer) var someBis: SomeClassable // THEN XCTAssertEqual($some, $someBis, "Equality of the property wrappers are expected to match") @@ -43,10 +43,10 @@ final class UTLazyInjectService_Identity: XCTestCase { ) } - func testTwoLazyInjectService_SameIdentityOfPropertyWrappers_ForStruct() { + func testTwoLazyInject_SameIdentityOfPropertyWrappers_ForStruct() { // GIVEN - @LazyInjectService var some: SomeStruct - @LazyInjectService var someBis: SomeStruct + @LazyInject(container: someContainer) var some: SomeStruct + @LazyInject(container: someContainer) var someBis: SomeStruct // THEN XCTAssertEqual($some, $someBis, "Equality of the property wrappers are expected to match") @@ -56,10 +56,10 @@ final class UTLazyInjectService_Identity: XCTestCase { ) } - func testTwoLazyInjectService_SameIdentityOfPropertyWrappers_ForEnum() { + func testTwoLazyInject_SameIdentityOfPropertyWrappers_ForEnum() { // GIVEN - @LazyInjectService var some: SomeEnum - @LazyInjectService var someBis: SomeEnum + @LazyInject(container: someContainer) var some: SomeEnum + @LazyInject(container: someContainer) var someBis: SomeEnum // THEN XCTAssertEqual($some, $someBis, "Equality of the property wrappers are expected to match") @@ -69,10 +69,10 @@ final class UTLazyInjectService_Identity: XCTestCase { ) } - func testTwoLazyInjectService_NotSameIdentityOfPropertyWrappers_BetweenTwoClasses() { + func testTwoLazyInject_NotSameIdentityOfPropertyWrappers_BetweenTwoClasses() { // GIVEN - @LazyInjectService var some: SomeClass - @LazyInjectService var someOther: SomeOtherClass + @LazyInject(container: someContainer) var some: SomeClass + @LazyInject(container: someContainer) var someOther: SomeOtherClass // THEN XCTAssertFalse( @@ -81,10 +81,10 @@ final class UTLazyInjectService_Identity: XCTestCase { ) } - func testTwoLazyInjectService_NotSameIdentityOfPropertyWrappers_BetweenClassAndProtocol() { + func testTwoLazyInject_NotSameIdentityOfPropertyWrappers_BetweenClassAndProtocol() { // GIVEN - @LazyInjectService var someClass: SomeClass - @LazyInjectService var someProtocol: SomeClassable + @LazyInject(container: someContainer) var someClass: SomeClass + @LazyInject(container: someContainer) var someProtocol: SomeClassable // THEN XCTAssertFalse( @@ -93,10 +93,10 @@ final class UTLazyInjectService_Identity: XCTestCase { ) } - func testTwoLazyInjectService_SameIdentityOfPropertyWrappers_BetweenProtocolAndConformingClass() { + func testTwoLazyInject_SameIdentityOfPropertyWrappers_BetweenProtocolAndConformingClass() { // GIVEN - @LazyInjectService var someClassConforming: SomeClassConforming - @LazyInjectService var someProtocol: SomeClassable + @LazyInject(container: someContainer) var someClassConforming: SomeClassConforming + @LazyInject(container: someContainer) var someProtocol: SomeClassable // THEN XCTAssertFalse( From b7ad8dc63322ce0db5ce43c69ed8ae34ca15e30d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adrien=20Coye=20de=20Brune=CC=81lis?= Date: Wed, 14 Aug 2024 16:16:12 +0200 Subject: [PATCH 3/3] feat: Re-added LazyInjectService InjectService for backward compatibility --- .../DependencyInjectionService.swift | 14 +--- .../{ => PropertyWrappers}/Inject.swift | 0 .../PropertyWrappers/InjectService.swift | 77 ++++++++++++++++++ .../{ => PropertyWrappers}/LazyInject.swift | 8 +- .../PropertyWrappers/LazyInjectService.swift | 79 +++++++++++++++++++ 5 files changed, 163 insertions(+), 15 deletions(-) rename Sources/InfomaniakDI/{ => PropertyWrappers}/Inject.swift (100%) create mode 100644 Sources/InfomaniakDI/PropertyWrappers/InjectService.swift rename Sources/InfomaniakDI/{ => PropertyWrappers}/LazyInject.swift (87%) create mode 100644 Sources/InfomaniakDI/PropertyWrappers/LazyInjectService.swift diff --git a/Sources/InfomaniakDI/DependencyInjectionService.swift b/Sources/InfomaniakDI/DependencyInjectionService.swift index b4bbf0e..dbe4ef9 100644 --- a/Sources/InfomaniakDI/DependencyInjectionService.swift +++ b/Sources/InfomaniakDI/DependencyInjectionService.swift @@ -18,16 +18,8 @@ import Foundation /// Allows to add and remove dynamically containers. /// Also provides a standard, shared, singleton style, container. public final class DependencyInjectionService { - /// Shared container of all singletons of an executable - var _sharedContainer: Container - - public var sharedContainer: Container { - _sharedContainer - } - - public init(sharedContainer: Container = Container()) { - self._sharedContainer = sharedContainer - } - + public static let sharedContainer = Container() + + public init() {} } diff --git a/Sources/InfomaniakDI/Inject.swift b/Sources/InfomaniakDI/PropertyWrappers/Inject.swift similarity index 100% rename from Sources/InfomaniakDI/Inject.swift rename to Sources/InfomaniakDI/PropertyWrappers/Inject.swift diff --git a/Sources/InfomaniakDI/PropertyWrappers/InjectService.swift b/Sources/InfomaniakDI/PropertyWrappers/InjectService.swift new file mode 100644 index 0000000..62bdec4 --- /dev/null +++ b/Sources/InfomaniakDI/PropertyWrappers/InjectService.swift @@ -0,0 +1,77 @@ +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +import Foundation + +/// A property wrapper that resolves shared types when the host type is initialized. +@propertyWrapper public struct InjectService: CustomDebugStringConvertible, Equatable, Identifiable { + /// Identifiable + /// + /// Something to link the identity of this property wrapper to the underlying Service type. + public let id = ObjectIdentifier(Injected.self) + + /// Equatable + /// + /// Two `InjectService` that points to the same `Injected` Metatype are expected to be equal (for the sake of SwiftUI + /// correctness) + public static func == (lhs: InjectService, rhs: InjectService) -> Bool { + return lhs.id == rhs.id + } + + public var debugDescription: String { + """ + <\(type(of: self)) + wrapping type:'\(Injected.self)' + customTypeIdentifier:\(String(describing: customTypeIdentifier)) + factoryParameters:\(String(describing: factoryParameters)) + id:\(id)'> + """ + } + + /// Store the resolved service + var service: Injected! + + public var container: Resolvable + public var customTypeIdentifier: String? + public var factoryParameters: [String: Any]? + + public init(customTypeIdentifier: String? = nil, + factoryParameters: [String: Any]? = nil) { + self.customTypeIdentifier = customTypeIdentifier + self.factoryParameters = factoryParameters + container = DependencyInjectionService.sharedContainer + + do { + service = try container.resolve(type: Injected.self, + forCustomTypeIdentifier: customTypeIdentifier, + factoryParameters: factoryParameters, + resolver: container) + } catch { + fatalError("DI fatal error :\(error)") + } + } + + public var wrappedValue: Injected { + get { + service + } + set { + fatalError("You are not expected to substitute resolved objects") + } + } + + /// The property wrapper itself for debugging and testing + public var projectedValue: Self { + self + } +} diff --git a/Sources/InfomaniakDI/LazyInject.swift b/Sources/InfomaniakDI/PropertyWrappers/LazyInject.swift similarity index 87% rename from Sources/InfomaniakDI/LazyInject.swift rename to Sources/InfomaniakDI/PropertyWrappers/LazyInject.swift index e9b5200..7c5a9e7 100644 --- a/Sources/InfomaniakDI/LazyInject.swift +++ b/Sources/InfomaniakDI/PropertyWrappers/LazyInject.swift @@ -14,7 +14,7 @@ import Foundation /// Inject a type at the first use of the property -@propertyWrapper public final class LazyInject: Equatable, Identifiable { +@propertyWrapper public class LazyInject: Equatable, Identifiable { /// Identifiable /// /// Something to link the identity of this property wrapper to the underlying Service type. @@ -61,9 +61,9 @@ import Foundation do { resolvedInstance = try container.resolve(type: Injected.self, - forCustomTypeIdentifier: customTypeIdentifier, - factoryParameters: factoryParameters, - resolver: container) + forCustomTypeIdentifier: customTypeIdentifier, + factoryParameters: factoryParameters, + resolver: container) return resolvedInstance! } catch { fatalError("DI fatal error :\(error)") diff --git a/Sources/InfomaniakDI/PropertyWrappers/LazyInjectService.swift b/Sources/InfomaniakDI/PropertyWrappers/LazyInjectService.swift new file mode 100644 index 0000000..ea07aff --- /dev/null +++ b/Sources/InfomaniakDI/PropertyWrappers/LazyInjectService.swift @@ -0,0 +1,79 @@ +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +import Foundation + +@propertyWrapper public class LazyInjectService: Equatable, Identifiable { + /// Identifiable + /// + /// Something to link the identity of this property wrapper to the underlying Service type. + public let id = ObjectIdentifier(Injected.self) + + /// Equatable + /// + /// Two `LazyInjectService` that points to the same `Service` Metatype are expected to be equal (for the sake of SwiftUI + /// correctness) + public static func == (lhs: LazyInjectService, rhs: LazyInjectService) -> Bool { + return lhs.id == rhs.id + } + + public var debugDescription: String { + """ + <\(type(of: self)) + wrapping type:'\(Injected.self)' + customTypeIdentifier:\(String(describing: customTypeIdentifier)) + factoryParameters:\(String(describing: factoryParameters)) + id:\(id)'> + """ + } + + /// Store the instance of the resolved type + var resolvedInstance: Injected? + + public var container: Resolvable + public var customTypeIdentifier: String? + public var factoryParameters: [String: Any]? + + public init(customTypeIdentifier: String? = nil, + factoryParameters: [String: Any]? = nil) { + self.customTypeIdentifier = customTypeIdentifier + self.factoryParameters = factoryParameters + container = DependencyInjectionService.sharedContainer + } + + public var wrappedValue: Injected { + get { + if let resolvedInstance { + return resolvedInstance + } + + do { + resolvedInstance = try container.resolve(type: Injected.self, + forCustomTypeIdentifier: customTypeIdentifier, + factoryParameters: factoryParameters, + resolver: container) + return resolvedInstance! + } catch { + fatalError("DI fatal error :\(error)") + } + } + set { + fatalError("You are not expected to substitute resolved objects") + } + } + + /// The property wrapper itself for debugging and testing + public var projectedValue: LazyInjectService { + self + } +}