From ffd98729d146e8b4b2c9a558ced3569e8f63d5da Mon Sep 17 00:00:00 2001 From: Cornelius Horstmann Date: Thu, 29 Dec 2022 10:42:05 +0100 Subject: [PATCH 1/4] Fixed initial build errors --- DIKit/DIKit.xcodeproj/project.pbxproj | 4 ++-- DIKitExample/Sources/AppDelegate.swift | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/DIKit/DIKit.xcodeproj/project.pbxproj b/DIKit/DIKit.xcodeproj/project.pbxproj index a289f87..1dd22a3 100644 --- a/DIKit/DIKit.xcodeproj/project.pbxproj +++ b/DIKit/DIKit.xcodeproj/project.pbxproj @@ -173,9 +173,9 @@ isa = PBXNativeTarget; buildConfigurationList = 4BD95D9C2008B1C9009CE38D /* Build configuration list for PBXNativeTarget "DIKit" */; buildPhases = ( + 4BD95D852008B1C8009CE38D /* Headers */, 4BD95D832008B1C8009CE38D /* Sources */, 4BD95D842008B1C8009CE38D /* Frameworks */, - 4BD95D852008B1C8009CE38D /* Headers */, 4BD95D862008B1C8009CE38D /* Resources */, A24242A72343AA95002FAD54 /* SwiftLint */, A24242A82343AAA0002FAD54 /* SwiftLint Autocorrect */, @@ -300,7 +300,7 @@ ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "if [ \"${CONFIGURATION}\" = \"Debug\" ]; then\nif which swiftlint >/dev/null; then\nswiftlint autocorrect\nelse\necho \"warning: SwiftLint not installed, download from https://github.com/realm/SwiftLint or run brew install swiftlint from your CLI\"\nfi\nelse\necho \"warning: Skipping SwiftLint autocorrect, this is not a Debug build\"\nfi\n"; + shellScript = "if [ \"${CONFIGURATION}\" = \"Debug\" ]; then\nif which swiftlint >/dev/null; then\nswiftlint autocorrect ./\nelse\necho \"warning: SwiftLint not installed, download from https://github.com/realm/SwiftLint or run brew install swiftlint from your CLI\"\nfi\nelse\necho \"warning: Skipping SwiftLint autocorrect, this is not a Debug build\"\nfi\n"; }; /* End PBXShellScriptBuildPhase section */ diff --git a/DIKitExample/Sources/AppDelegate.swift b/DIKitExample/Sources/AppDelegate.swift index 5197367..77f2e15 100644 --- a/DIKitExample/Sources/AppDelegate.swift +++ b/DIKitExample/Sources/AppDelegate.swift @@ -18,6 +18,6 @@ class AppDelegate: UIResponder, UIApplicationDelegate { override init() { super.init() - DependencyContainer.defined(by: modules { .app; .backend }) + DependencyContainer.defined(by: modules { DependencyContainer.app; DependencyContainer.backend }) } } From 785c9f2b447427913ed2a6cb1fb54e5a5570a3c4 Mon Sep 17 00:00:00 2001 From: Cornelius Horstmann Date: Thu, 29 Dec 2022 11:12:19 +0100 Subject: [PATCH 2/4] Implemented factories with a custom argument --- DIKit/Sources/Component/Component.swift | 12 ++++++------ .../DependencyContainer+Register.swift | 8 +++++++- .../Container/DependencyContainer+Resolve.swift | 17 ++++++++++++++--- DIKit/Sources/DIKit+Inject.swift | 4 ++++ DIKit/Sources/DIKit.swift | 7 +++++++ DIKit/Sources/DIKitDSL.swift | 8 ++++++++ .../Sources/DependencyContainer+App.swift | 6 ++++-- .../LocalStorageLayer/LocalStorage.swift | 4 +++- .../LocalStorageProtocol.swift | 1 + .../Sources/Views/FirstViewController.swift | 3 +++ 10 files changed, 57 insertions(+), 13 deletions(-) diff --git a/DIKit/Sources/Component/Component.swift b/DIKit/Sources/Component/Component.swift index 490c3c2..754a6fc 100644 --- a/DIKit/Sources/Component/Component.swift +++ b/DIKit/Sources/Component/Component.swift @@ -7,26 +7,26 @@ // // Copyright © 2018 Ben John. All rights reserved. -public typealias ComponentFactory = () -> Any +public typealias ComponentFactory = (Any?) -> Any -class Component: ComponentProtocol { +class Component: ComponentProtocol { let lifetime: Lifetime let identifier: AnyHashable let type: Any.Type let componentFactory: ComponentFactory - init(lifetime: Lifetime, factory: @escaping () -> T) { + init(lifetime: Lifetime, factory: @escaping (_ argument: A) -> T) { self.lifetime = lifetime self.identifier = ComponentIdentifier(type: T.self) self.type = T.self - self.componentFactory = { factory() } + self.componentFactory = { factory($0 as! A) } // swiftlint:disable:this force_cast } - init(lifetime: Lifetime, tag: AnyHashable, factory: @escaping () -> T) { + init(lifetime: Lifetime, tag: AnyHashable, factory: @escaping (_ argument: A) -> T) { self.lifetime = lifetime self.identifier = ComponentIdentifier(tag: tag, type: T.self) self.type = T.self - self.componentFactory = { factory() } + self.componentFactory = { factory($0 as! A) } // swiftlint:disable:this force_cast } } diff --git a/DIKit/Sources/Container/DependencyContainer+Register.swift b/DIKit/Sources/Container/DependencyContainer+Register.swift index a0aa01c..fa06c0d 100644 --- a/DIKit/Sources/Container/DependencyContainer+Register.swift +++ b/DIKit/Sources/Container/DependencyContainer+Register.swift @@ -13,7 +13,7 @@ extension DependencyContainer { /// - Parameters: /// - lifetime: The *scope* of the `Component`, defaults to `Lifetime.singleton`. /// - factory: The *factory* for the initialization of the `Component`. - public func register(lifetime: Lifetime = .singleton, _ factory: @escaping () -> T) { + public func register(lifetime: Lifetime = .singleton, _ factory: @escaping (A) -> T) { let component = Component(lifetime: lifetime, factory: factory) register(component) } @@ -38,4 +38,10 @@ extension DependencyContainer { self.componentStack[component.identifier] = component } } + + /// Convenience function without any arguments + public func register(lifetime: Lifetime = .singleton, _ factory: @escaping () -> T) { + let newFactory: (Any) -> T = { _ in factory() } + register(lifetime: lifetime, newFactory) + } } diff --git a/DIKit/Sources/Container/DependencyContainer+Resolve.swift b/DIKit/Sources/Container/DependencyContainer+Resolve.swift index 21b1ce8..7b64204 100644 --- a/DIKit/Sources/Container/DependencyContainer+Resolve.swift +++ b/DIKit/Sources/Container/DependencyContainer+Resolve.swift @@ -12,18 +12,18 @@ extension DependencyContainer { /// /// - Parameter tag: An optional *tag* to identify the Component. `nil` per default. /// - Returns: The resolved `Optional>`. - func _resolve(tag: AnyHashable? = nil) -> T? { + func _resolve(_ argument: A, tag: AnyHashable? = nil) -> T? { let identifier = ComponentIdentifier(tag: tag, type: T.self) guard let foundComponent = self.componentStack[identifier] else { return nil } if foundComponent.lifetime == .factory { - return foundComponent.componentFactory() as? T + return foundComponent.componentFactory(argument) as? T } if let instanceOfComponent = self.instanceStack[identifier] as? T { return instanceOfComponent } - let instance = foundComponent.componentFactory() as! T + let instance = foundComponent.componentFactory(argument) as! T self.instanceStack[identifier] = instance return instance } @@ -48,10 +48,21 @@ extension DependencyContainer { /// - Parameter tag: An optional *tag* to identify the Component. `nil` per default. /// /// - Returns: The resolved `Component`. + public func resolve(_ argument: A, tag: AnyHashable? = nil) -> T { + if let t: T = _resolve(argument, tag: tag) { + return t + } + fatalError("Component `\(String(describing: T.self))` could not be resolved.") + } + + /// Convenience function without any arguments public func resolve(tag: AnyHashable? = nil) -> T { if let t: T = _resolve(tag: tag) { return t } fatalError("Component `\(String(describing: T.self))` could not be resolved.") } + func _resolve(tag: AnyHashable? = nil) -> T? { + _resolve((), tag: tag) + } } diff --git a/DIKit/Sources/DIKit+Inject.swift b/DIKit/Sources/DIKit+Inject.swift index ef9451a..7a3d938 100644 --- a/DIKit/Sources/DIKit+Inject.swift +++ b/DIKit/Sources/DIKit+Inject.swift @@ -49,6 +49,10 @@ public struct Inject { public init(tag: AnyHashable? = nil) { self.wrappedValue = resolve(tag: tag) } + + public init(_ argument: A) { + self.wrappedValue = resolve(argument) + } } /// A property wrapper (SE-0258) to make a `Optional` injectable diff --git a/DIKit/Sources/DIKit.swift b/DIKit/Sources/DIKit.swift index c645831..18e0e55 100644 --- a/DIKit/Sources/DIKit.swift +++ b/DIKit/Sources/DIKit.swift @@ -13,6 +13,8 @@ /// - Returns: The resolved `Component`. public func resolve(tag: AnyHashable? = nil) -> T { DependencyContainer.shared.resolve(tag: tag) } +public func resolve(_ argument: A, tag: AnyHashable? = nil) -> T { DependencyContainer.shared.resolve(argument, tag: tag) } + /// Resolves nil safe given `Component`. /// /// - Parameter tag: An optional *tag* to identify the Component. `nil` per default. @@ -21,3 +23,8 @@ public func resolveOptional(tag: AnyHashable? = nil) -> T? { guard DependencyContainer.shared.resolvable(type: T.self, tag: tag) else { return nil } return DependencyContainer.shared._resolve(tag: tag) } + +public func resolveOptional(_ argument: A, tag: AnyHashable? = nil) -> T? { + guard DependencyContainer.shared.resolvable(type: T.self, tag: tag) else { return nil } + return DependencyContainer.shared._resolve(argument, tag: tag) +} diff --git a/DIKit/Sources/DIKitDSL.swift b/DIKit/Sources/DIKitDSL.swift index da60483..d42c33f 100644 --- a/DIKit/Sources/DIKitDSL.swift +++ b/DIKit/Sources/DIKitDSL.swift @@ -41,6 +41,10 @@ public func resolvable(lifetime: Lifetime = .singleton, _ factory: @escaping Component(lifetime: lifetime, factory: factory) as ComponentProtocol } +public func resolvable(lifetime: Lifetime = .singleton, _ factory: @escaping (_ argument: A) -> T) -> ComponentProtocol { + Component(lifetime: lifetime, factory: factory) as ComponentProtocol +} + public func resolvable( lifetime: Lifetime = .singleton, tag: AnyHashable, @@ -53,6 +57,10 @@ public func factory(factory: @escaping () -> T) -> [ComponentProtocol] { [resolvable(lifetime: .factory, factory)] } +public func factory(factory: @escaping (_ argument: A) -> T) -> [ComponentProtocol] { + [resolvable(lifetime: .factory, factory)] +} + public func factory(tag: AnyHashable, factory: @escaping () -> T) -> [ComponentProtocol] { [resolvable(lifetime: .factory, tag: tag, factory)] } diff --git a/DIKitExample/Sources/DependencyContainer+App.swift b/DIKitExample/Sources/DependencyContainer+App.swift index 86d5fe7..d870f83 100644 --- a/DIKitExample/Sources/DependencyContainer+App.swift +++ b/DIKitExample/Sources/DependencyContainer+App.swift @@ -13,7 +13,9 @@ import DIKit public extension DependencyContainer { static var app = module { // We create two different kind of storages. One for both kind of contexts. - factory(tag: StorageContext.systemdata) { LocalStorage() as LocalStorageProtocol } - factory(tag: StorageContext.userdata) { LocalStorage() as LocalStorageProtocol } + factory(tag: StorageContext.systemdata) { LocalStorage(name: "system") as LocalStorageProtocol } + factory(tag: StorageContext.userdata) { LocalStorage(name: "userdata") as LocalStorageProtocol } + + factory { name in LocalStorage(name: name ?? "default") as LocalStorageProtocol } } } diff --git a/DIKitExample/Sources/LocalStorageLayer/LocalStorage.swift b/DIKitExample/Sources/LocalStorageLayer/LocalStorage.swift index bf13075..3365b2f 100644 --- a/DIKitExample/Sources/LocalStorageLayer/LocalStorage.swift +++ b/DIKitExample/Sources/LocalStorageLayer/LocalStorage.swift @@ -12,8 +12,10 @@ class LocalStorage: LocalStorageProtocol { var id: ObjectIdentifier { return ObjectIdentifier.init(self) } + let name: String - init() { + init(name: String) { + self.name = name print("LocalStorage init") print("LocalStorage instance \(ObjectIdentifier.init(self))") } diff --git a/DIKitExample/Sources/LocalStorageLayer/LocalStorageProtocol.swift b/DIKitExample/Sources/LocalStorageLayer/LocalStorageProtocol.swift index dc7dace..2826c61 100644 --- a/DIKitExample/Sources/LocalStorageLayer/LocalStorageProtocol.swift +++ b/DIKitExample/Sources/LocalStorageLayer/LocalStorageProtocol.swift @@ -10,4 +10,5 @@ protocol LocalStorageProtocol { var id: ObjectIdentifier { get } + var name: String { get } } diff --git a/DIKitExample/Sources/Views/FirstViewController.swift b/DIKitExample/Sources/Views/FirstViewController.swift index 58ba81b..bee627d 100644 --- a/DIKitExample/Sources/Views/FirstViewController.swift +++ b/DIKitExample/Sources/Views/FirstViewController.swift @@ -15,10 +15,13 @@ import DIKitExampleBackend class FirstViewController: UIViewController { // MARK: - DIKit @Inject var backend: BackendProtocol + @Inject("Local Storage with a custom name") var localStorage: LocalStorageProtocol // MARK: - View lifecycle override func viewWillAppear(_ animated: Bool) { let result = backend.fetch() print(result) + + print(localStorage.name) } } From 2a252749d68c1cda1c4c7a1833bbbd46f50c91b4 Mon Sep 17 00:00:00 2001 From: Cornelius Horstmann Date: Thu, 29 Dec 2022 11:58:12 +0100 Subject: [PATCH 3/4] Added a way to define dependencies with different arguments and same result type --- DIKit/Sources/Component/Component.swift | 11 +++++++---- .../Container/DependencyContainer+Resolve.swift | 4 ++-- DIKitExample/Sources/DependencyContainer+App.swift | 4 +++- DIKitExample/Sources/Views/FirstViewController.swift | 8 ++++++-- 4 files changed, 18 insertions(+), 9 deletions(-) diff --git a/DIKit/Sources/Component/Component.swift b/DIKit/Sources/Component/Component.swift index 754a6fc..9dc86aa 100644 --- a/DIKit/Sources/Component/Component.swift +++ b/DIKit/Sources/Component/Component.swift @@ -17,14 +17,14 @@ class Component: ComponentProtocol { init(lifetime: Lifetime, factory: @escaping (_ argument: A) -> T) { self.lifetime = lifetime - self.identifier = ComponentIdentifier(type: T.self) + self.identifier = ComponentIdentifier(type: T.self, argumentType: A.self) self.type = T.self self.componentFactory = { factory($0 as! A) } // swiftlint:disable:this force_cast } init(lifetime: Lifetime, tag: AnyHashable, factory: @escaping (_ argument: A) -> T) { self.lifetime = lifetime - self.identifier = ComponentIdentifier(tag: tag, type: T.self) + self.identifier = ComponentIdentifier(tag: tag, type: T.self, argumentType: A.self) self.type = T.self self.componentFactory = { factory($0 as! A) } // swiftlint:disable:this force_cast } @@ -33,22 +33,25 @@ class Component: ComponentProtocol { struct ComponentIdentifier: Hashable { let tag: AnyHashable? let type: Any.Type + let argumentType: Any.Type func hash(into hasher: inout Hasher) { hasher.combine(String(describing: type)) + hasher.combine(String(describing: argumentType)) if let tag = tag { hasher.combine(tag) } } static func == (lhs: ComponentIdentifier, rhs: ComponentIdentifier) -> Bool { - lhs.type == rhs.type && lhs.tag == rhs.tag + lhs.type == rhs.type && lhs.tag == rhs.tag && lhs.argumentType == rhs.argumentType } } extension ComponentIdentifier { - init(type: Any.Type) { + init(type: Any.Type, argumentType: Any.Type) { self.type = type + self.argumentType = argumentType self.tag = nil } } diff --git a/DIKit/Sources/Container/DependencyContainer+Resolve.swift b/DIKit/Sources/Container/DependencyContainer+Resolve.swift index 7b64204..6433081 100644 --- a/DIKit/Sources/Container/DependencyContainer+Resolve.swift +++ b/DIKit/Sources/Container/DependencyContainer+Resolve.swift @@ -13,7 +13,7 @@ extension DependencyContainer { /// - Parameter tag: An optional *tag* to identify the Component. `nil` per default. /// - Returns: The resolved `Optional>`. func _resolve(_ argument: A, tag: AnyHashable? = nil) -> T? { - let identifier = ComponentIdentifier(tag: tag, type: T.self) + let identifier = ComponentIdentifier(tag: tag, type: T.self, argumentType: A.self) guard let foundComponent = self.componentStack[identifier] else { return nil } @@ -37,7 +37,7 @@ extension DependencyContainer { /// /// - Returns: `Bool` whether `Component` is resolvable or not. func resolvable(type: T.Type, tag: AnyHashable? = nil) -> Bool { - let identifier = ComponentIdentifier(tag: tag, type: T.self) + let identifier = ComponentIdentifier(tag: tag, type: T.self, argumentType: Void.self) return self.componentStack[identifier] != nil } diff --git a/DIKitExample/Sources/DependencyContainer+App.swift b/DIKitExample/Sources/DependencyContainer+App.swift index d870f83..6f36f76 100644 --- a/DIKitExample/Sources/DependencyContainer+App.swift +++ b/DIKitExample/Sources/DependencyContainer+App.swift @@ -16,6 +16,8 @@ public extension DependencyContainer { factory(tag: StorageContext.systemdata) { LocalStorage(name: "system") as LocalStorageProtocol } factory(tag: StorageContext.userdata) { LocalStorage(name: "userdata") as LocalStorageProtocol } - factory { name in LocalStorage(name: name ?? "default") as LocalStorageProtocol } + factory { LocalStorage(name: "default") as LocalStorageProtocol } + factory { name in LocalStorage(name: name) as LocalStorageProtocol } + factory { (args: (name: String, other: Int)) in LocalStorage(name: args.name) as LocalStorageProtocol } } } diff --git a/DIKitExample/Sources/Views/FirstViewController.swift b/DIKitExample/Sources/Views/FirstViewController.swift index bee627d..9883c26 100644 --- a/DIKitExample/Sources/Views/FirstViewController.swift +++ b/DIKitExample/Sources/Views/FirstViewController.swift @@ -15,13 +15,17 @@ import DIKitExampleBackend class FirstViewController: UIViewController { // MARK: - DIKit @Inject var backend: BackendProtocol - @Inject("Local Storage with a custom name") var localStorage: LocalStorageProtocol + @Inject var defaultLocalStorage: LocalStorageProtocol + @Inject("With just one parameter") var localStorageWithJustOneParameter: LocalStorageProtocol + @Inject((name: "Local Storage with multiple parameters", other: 1)) var localStorageWithMultipleParameters: LocalStorageProtocol // MARK: - View lifecycle override func viewWillAppear(_ animated: Bool) { let result = backend.fetch() print(result) - print(localStorage.name) + print(defaultLocalStorage.name) + print(localStorageWithJustOneParameter.name) + print(localStorageWithMultipleParameters.name) } } From f88066cde05741fa84fb43d014036582e065383a Mon Sep 17 00:00:00 2001 From: Cornelius Horstmann Date: Thu, 29 Dec 2022 12:02:36 +0100 Subject: [PATCH 4/4] Fixed issues in Unit Tests --- .../Container/DependencyContainer+Register.swift | 2 +- DIKit/Tests/DependencyContainerTests.swift | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/DIKit/Sources/Container/DependencyContainer+Register.swift b/DIKit/Sources/Container/DependencyContainer+Register.swift index fa06c0d..b0b953c 100644 --- a/DIKit/Sources/Container/DependencyContainer+Register.swift +++ b/DIKit/Sources/Container/DependencyContainer+Register.swift @@ -41,7 +41,7 @@ extension DependencyContainer { /// Convenience function without any arguments public func register(lifetime: Lifetime = .singleton, _ factory: @escaping () -> T) { - let newFactory: (Any) -> T = { _ in factory() } + let newFactory: (()) -> T = { _ in factory() } register(lifetime: lifetime, newFactory) } } diff --git a/DIKit/Tests/DependencyContainerTests.swift b/DIKit/Tests/DependencyContainerTests.swift index a743c8f..0eb67fa 100644 --- a/DIKit/Tests/DependencyContainerTests.swift +++ b/DIKit/Tests/DependencyContainerTests.swift @@ -20,33 +20,33 @@ class DependencyContainerTests: XCTestCase { c.register(tag: "tag") { ComponentB() } } - let componentAIdentifier = ComponentIdentifier(type: ComponentA.self) + let componentAIdentifier = ComponentIdentifier(type: ComponentA.self, argumentType: Void.self) guard let componentA = dependencyContainer.componentStack.index(forKey: componentAIdentifier) else { return XCTFail("ComponentStack does not contain `ComponentA`.") } let componentProtocolA = dependencyContainer.componentStack[componentA].value XCTAssertEqual(componentProtocolA.lifetime, .singleton) - let instanceA = componentProtocolA.componentFactory() + let instanceA = componentProtocolA.componentFactory(()) XCTAssertTrue(instanceA is ComponentA) XCTAssertFalse(instanceA is ComponentB) - let componentBIdentifier = ComponentIdentifier(type: ComponentB.self) + let componentBIdentifier = ComponentIdentifier(type: ComponentB.self, argumentType: Void.self) guard let componentB = dependencyContainer.componentStack.index(forKey: componentBIdentifier) else { return XCTFail("ComponentStack does not contain `ComponentB`.") } let componentProtocolB = dependencyContainer.componentStack[componentB].value XCTAssertEqual(componentProtocolB.lifetime, .singleton) - let instanceB = componentProtocolB.componentFactory() + let instanceB = componentProtocolB.componentFactory(()) XCTAssertTrue(instanceB is ComponentB) XCTAssertFalse(instanceB is ComponentA) - let taggedComponentBIdentifier = ComponentIdentifier(tag: "tag", type: ComponentB.self) + let taggedComponentBIdentifier = ComponentIdentifier(tag: "tag", type: ComponentB.self, argumentType: Void.self) guard let taggedComponentB = dependencyContainer.componentStack.index(forKey: taggedComponentBIdentifier) else { return XCTFail("ComponentStack does not contain `ComponentB`.") } let taggedComponentProtocolB = dependencyContainer.componentStack[taggedComponentB].value XCTAssertEqual(taggedComponentProtocolB.lifetime, .singleton) - let taggedInstanceB = taggedComponentProtocolB.componentFactory() + let taggedInstanceB = taggedComponentProtocolB.componentFactory(()) XCTAssertTrue(taggedInstanceB is ComponentB) XCTAssertFalse(taggedInstanceB is ComponentA) }