From b5376f4f94274f5a968310c13656b03180fec072 Mon Sep 17 00:00:00 2001 From: Kai Maschke Date: Tue, 26 Apr 2022 20:26:47 +0200 Subject: [PATCH 1/9] Added function resolveAll to get all instances of a specific type ignoring their names --- Sources/Resolver/Resolver.swift | 38 +++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/Sources/Resolver/Resolver.swift b/Sources/Resolver/Resolver.swift index 975bbbe..61162df 100644 --- a/Sources/Resolver/Resolver.swift +++ b/Sources/Resolver/Resolver.swift @@ -261,6 +261,24 @@ public final class Resolver { fatalError("RESOLVER: '\(Service.self):\(name?.rawValue ?? "NONAME")' not resolved. To disambiguate optionals use resolver.optional().") } + // Resolves and returns all named instances of the given Service type from the current registry or from its + /// parent registries. + /// + /// - parameter type: Type of Services being resolved. Optional, may be inferred by assignment result type. + /// - parameter args: Optional arguments that may be passed to registration factory. + /// + /// - returns: Instances of specified Service. + /// + public final func resolveAll(_ type: Service.Type = Service.self, args: Any? = nil) -> [Service] { + lock.lock() + defer { lock.unlock() } + registrationCheck() + if let registrations = lookupAll(type) { + return registrations.compactMap { reg in return reg.scope.resolve(resolver: self, registration: reg, args: args) } + } + fatalError("RESOLVER: '\(Service.self)' types not resolved.") + } + /// Static function calls the root registry to resolve an optional Service type. /// /// - parameter type: Type of Service being resolved. Optional, may be inferred by assignment result type. @@ -319,6 +337,26 @@ public final class Resolver { return nil } + /// Internal function searches the current and child registries for all ResolverRegistrations that matches + /// the supplied type. + private final func lookupAll(_ type: Service.Type) -> [ResolverRegistration]? { + let key = Int(bitPattern: ObjectIdentifier(Service.self)) + let all = namedRegistrations.filter { reg in + return reg.key.hasPrefix("\(key):") + }.compactMap { key, value in + return value as? ResolverRegistration + } + if all.isEmpty { + for child in childContainers { + if let registration = child.lookupAll(type) { + return registration + } + } + return nil + } + return all + } + /// Internal function adds a new registration to the proper container. private final func add(registration: ResolverRegistration, with key: Int, name: Resolver.Name?) { if let name = name?.rawValue { From b8c6c80cc5bcb7194962d4a45dca34af05fdbd87 Mon Sep 17 00:00:00 2001 From: Kai Maschke Date: Tue, 26 Apr 2022 20:34:22 +0200 Subject: [PATCH 2/9] Fixed function signature issue --- Sources/Resolver/Resolver.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/Resolver/Resolver.swift b/Sources/Resolver/Resolver.swift index 61162df..c3c8743 100644 --- a/Sources/Resolver/Resolver.swift +++ b/Sources/Resolver/Resolver.swift @@ -274,7 +274,7 @@ public final class Resolver { defer { lock.unlock() } registrationCheck() if let registrations = lookupAll(type) { - return registrations.compactMap { reg in return reg.scope.resolve(resolver: self, registration: reg, args: args) } + return registrations.compactMap { reg in return reg.scope.resolve(registration: reg, resolver: self, args: args) } } fatalError("RESOLVER: '\(Service.self)' types not resolved.") } From 7e813a4338a8adb3403c84804c0e782b78ea5d9b Mon Sep 17 00:00:00 2001 From: Kai Maschke Date: Tue, 26 Apr 2022 20:44:31 +0200 Subject: [PATCH 3/9] Added static variant of resolveAll --- Sources/Resolver/Resolver.swift | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/Sources/Resolver/Resolver.swift b/Sources/Resolver/Resolver.swift index c3c8743..ba60c99 100644 --- a/Sources/Resolver/Resolver.swift +++ b/Sources/Resolver/Resolver.swift @@ -261,6 +261,24 @@ public final class Resolver { fatalError("RESOLVER: '\(Service.self):\(name?.rawValue ?? "NONAME")' not resolved. To disambiguate optionals use resolver.optional().") } + // Resolves and returns all named instances of the given Service type from the current registry or from its + /// parent registries. + /// + /// - parameter type: Type of Services being resolved. Optional, may be inferred by assignment result type. + /// - parameter args: Optional arguments that may be passed to registration factory. + /// + /// - returns: Instances of specified Service. + /// + public static func resolveAll(_ type: Service.Type = Service.self, args: Any? = nil) -> [Service] { + lock.lock() + defer { lock.unlock() } + registrationCheck() + if let registrations = root.lookupAll(type) { + return registrations.compactMap { reg in return reg.scope.resolve(registration: reg, resolver: self, args: args) } + } + fatalError("RESOLVER: '\(Service.self)' types not resolved.") + } + // Resolves and returns all named instances of the given Service type from the current registry or from its /// parent registries. /// From 8a62cbcd20ade2e42644e659994d3ef88caef647 Mon Sep 17 00:00:00 2001 From: Kai Maschke Date: Tue, 26 Apr 2022 20:53:12 +0200 Subject: [PATCH 4/9] Fixed an issue --- Sources/Resolver/Resolver.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/Resolver/Resolver.swift b/Sources/Resolver/Resolver.swift index ba60c99..78654fb 100644 --- a/Sources/Resolver/Resolver.swift +++ b/Sources/Resolver/Resolver.swift @@ -274,7 +274,7 @@ public final class Resolver { defer { lock.unlock() } registrationCheck() if let registrations = root.lookupAll(type) { - return registrations.compactMap { reg in return reg.scope.resolve(registration: reg, resolver: self, args: args) } + return registrations.compactMap { reg in return reg.scope.resolve(resolver: root, args: args) } } fatalError("RESOLVER: '\(Service.self)' types not resolved.") } From 3d7aedc511a8c87d71d1beec70103dfe4ea0860b Mon Sep 17 00:00:00 2001 From: Kai Maschke Date: Tue, 26 Apr 2022 20:55:06 +0200 Subject: [PATCH 5/9] Getting it fixed --- Sources/Resolver/Resolver.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/Resolver/Resolver.swift b/Sources/Resolver/Resolver.swift index 78654fb..db7a326 100644 --- a/Sources/Resolver/Resolver.swift +++ b/Sources/Resolver/Resolver.swift @@ -274,7 +274,7 @@ public final class Resolver { defer { lock.unlock() } registrationCheck() if let registrations = root.lookupAll(type) { - return registrations.compactMap { reg in return reg.scope.resolve(resolver: root, args: args) } + return registrations.compactMap { reg in return reg.resolve(resolver: root, args: args) } } fatalError("RESOLVER: '\(Service.self)' types not resolved.") } From fee5e15396daafe296a692ff562584ee9659ad7c Mon Sep 17 00:00:00 2001 From: Kai Maschke Date: Thu, 28 Apr 2022 15:16:24 +0200 Subject: [PATCH 6/9] Added unit test --- Tests/ResolverTests/ResolverNameTests.swift | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/Tests/ResolverTests/ResolverNameTests.swift b/Tests/ResolverTests/ResolverNameTests.swift index 88de734..e9d919b 100644 --- a/Tests/ResolverTests/ResolverNameTests.swift +++ b/Tests/ResolverTests/ResolverNameTests.swift @@ -183,4 +183,23 @@ class ResolverNameTests: XCTestCase { XCTAssert(barney?.name == "Barney") } + func testResolverAllOfType() { + + resolver.register(name: .fred) { XYZNameService("Fred") } + resolver.register(name: .barney) { XYZNameService("Barney") } + + let fredAndBarney: [XYZNameService] = resolver.resolveAll() + + // Check all services resolved + XCTAssertEqual(fredAndBarney.count, 2) + var foundFred = false + var foundBarney = false + for service in fredAndBarney { + foundFred = foundFred || service.name == "Fred" + foundBarney = foundBarney || service.name == "Barney" + } + + XCTAssertTrue(foundFred) + XCTAssertTrue(foundBarney) + } } From fedb87c00d00dee5a4ed00da9a6ce986f5374725 Mon Sep 17 00:00:00 2001 From: Kai Maschke Date: Thu, 28 Apr 2022 17:55:20 +0200 Subject: [PATCH 7/9] Traversing through all child containers when resolving all implementations of some kind --- Sources/Resolver/Resolver.swift | 15 +++++-------- Tests/ResolverTests/ResolverNameTests.swift | 25 ++++++++++++++++++++- 2 files changed, 30 insertions(+), 10 deletions(-) diff --git a/Sources/Resolver/Resolver.swift b/Sources/Resolver/Resolver.swift index db7a326..ee5a827 100644 --- a/Sources/Resolver/Resolver.swift +++ b/Sources/Resolver/Resolver.swift @@ -359,18 +359,15 @@ public final class Resolver { /// the supplied type. private final func lookupAll(_ type: Service.Type) -> [ResolverRegistration]? { let key = Int(bitPattern: ObjectIdentifier(Service.self)) - let all = namedRegistrations.filter { reg in - return reg.key.hasPrefix("\(key):") - }.compactMap { key, value in + var all = namedRegistrations.filter { registration in + return registration.key.hasPrefix("\(key):") + }.compactMap { _, value in return value as? ResolverRegistration } - if all.isEmpty { - for child in childContainers { - if let registration = child.lookupAll(type) { - return registration - } + for child in childContainers { + if let registrations = child.lookupAll(type) { + all.append(contentsOf: registrations) } - return nil } return all } diff --git a/Tests/ResolverTests/ResolverNameTests.swift b/Tests/ResolverTests/ResolverNameTests.swift index e9d919b..3d7d1a2 100644 --- a/Tests/ResolverTests/ResolverNameTests.swift +++ b/Tests/ResolverTests/ResolverNameTests.swift @@ -183,7 +183,7 @@ class ResolverNameTests: XCTestCase { XCTAssert(barney?.name == "Barney") } - func testResolverAllOfType() { + func testResolveAllOfType() { resolver.register(name: .fred) { XYZNameService("Fred") } resolver.register(name: .barney) { XYZNameService("Barney") } @@ -202,4 +202,27 @@ class ResolverNameTests: XCTestCase { XCTAssertTrue(foundFred) XCTAssertTrue(foundBarney) } + + func testResolveAllOfTypeInMultipleContainers() { + + let childResolver = Resolver() + let parentResolver = Resolver(child: childResolver) + + parentResolver.register(name: .fred) { XYZNameService("Fred") } + childResolver.register(name: .barney) { XYZNameService("Barney") } + + let fredAndBarney: [XYZNameService] = parentResolver.resolveAll() + + // Check all services resolved + XCTAssertEqual(fredAndBarney.count, 2) + var foundFred = false + var foundBarney = false + for service in fredAndBarney { + foundFred = foundFred || service.name == "Fred" + foundBarney = foundBarney || service.name == "Barney" + } + + XCTAssertTrue(foundFred) + XCTAssertTrue(foundBarney) + } } From 70865ca282d7c935494ec801868ad12352dd5b66 Mon Sep 17 00:00:00 2001 From: Kai Maschke Date: Mon, 9 May 2022 11:44:06 +0200 Subject: [PATCH 8/9] Making sure to only return all named services of a specific type by correctly respecting the container hierarchy. --- Sources/Resolver/Resolver.swift | 20 +++++++++++------ Tests/ResolverTests/ResolverNameTests.swift | 24 +++++++++++++++++++++ Tests/ResolverTests/TestData.swift | 10 ++++++++- 3 files changed, 47 insertions(+), 7 deletions(-) diff --git a/Sources/Resolver/Resolver.swift b/Sources/Resolver/Resolver.swift index ee5a827..6aee98c 100644 --- a/Sources/Resolver/Resolver.swift +++ b/Sources/Resolver/Resolver.swift @@ -358,18 +358,26 @@ public final class Resolver { /// Internal function searches the current and child registries for all ResolverRegistrations that matches /// the supplied type. private final func lookupAll(_ type: Service.Type) -> [ResolverRegistration]? { + guard let values = lookupAllKeyed(type)?.values else { + return nil + } + return Array(values) + } + + /// Internal function searches the current and child registries for all ResolverRegistrations that matches + /// the supplied type combind with their registered names. + private final func lookupAllKeyed(_ type: Service.Type) -> [String: ResolverRegistration]? { let key = Int(bitPattern: ObjectIdentifier(Service.self)) - var all = namedRegistrations.filter { registration in + var result = namedRegistrations.filter { registration in return registration.key.hasPrefix("\(key):") - }.compactMap { _, value in - return value as? ResolverRegistration } for child in childContainers { - if let registrations = child.lookupAll(type) { - all.append(contentsOf: registrations) + guard let childRegistrations = child.lookupAllKeyed(type) else { + continue } + result.merge(childRegistrations, uniquingKeysWith: { parentEntry, _ in return parentEntry }) } - return all + return result as? [String: ResolverRegistration] } /// Internal function adds a new registration to the proper container. diff --git a/Tests/ResolverTests/ResolverNameTests.swift b/Tests/ResolverTests/ResolverNameTests.swift index 3d7d1a2..024fb9b 100644 --- a/Tests/ResolverTests/ResolverNameTests.swift +++ b/Tests/ResolverTests/ResolverNameTests.swift @@ -225,4 +225,28 @@ class ResolverNameTests: XCTestCase { XCTAssertTrue(foundFred) XCTAssertTrue(foundBarney) } + + func testResolveAllOfTypeInMultipleContainersWithOverride() { + + let childResolver = Resolver() + let parentResolver = Resolver(child: childResolver) + + parentResolver.register(name: .fred) { XYZEnhancedNameService("Fred") as XYZNameProtocol } + childResolver.register(name: .fred) { XYZNameService("Fred") as XYZNameProtocol } + parentResolver.register(name: .barney) { XYZNameService("Barney") as XYZNameProtocol } + + let fredAndBarney: [XYZNameProtocol] = parentResolver.resolveAll() + + // Check all services resolved + XCTAssertEqual(fredAndBarney.count, 2) + var foundFredFromParent = false + var foundBarney = false + for service in fredAndBarney { + foundFredFromParent = foundFredFromParent || (service.name == "Fred" && service is XYZEnhancedNameService) + foundBarney = foundBarney || service.name == "Barney" + } + + XCTAssertTrue(foundFredFromParent) + XCTAssertTrue(foundBarney) + } } diff --git a/Tests/ResolverTests/TestData.swift b/Tests/ResolverTests/TestData.swift index ead4bc1..3b7bfcf 100644 --- a/Tests/ResolverTests/TestData.swift +++ b/Tests/ResolverTests/TestData.swift @@ -81,13 +81,21 @@ class XYZSessionService: XYZSessionProtocol { var name: String = "XYZSessionService" } -class XYZNameService { +protocol XYZNameProtocol { + var id: UUID { get } + var name: String { get set } +} + +class XYZNameService: XYZNameProtocol { let id = UUID() var name: String init(_ name: String) { self.name = name } } +class XYZEnhancedNameService: XYZNameService { + let isEnhanced: Bool = true +} class XYZNeverService { } From bd57198f43f4ee4514b8d94aaa46a171efdc967d Mon Sep 17 00:00:00 2001 From: Kai Maschke Date: Mon, 1 Aug 2022 08:46:37 +0200 Subject: [PATCH 9/9] Addressed PR feedback: no longer throwing error when no service can be found for looking up multiple implementations and fixed registration key --- Sources/Resolver/Resolver.swift | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Sources/Resolver/Resolver.swift b/Sources/Resolver/Resolver.swift index 6aee98c..2092bf9 100644 --- a/Sources/Resolver/Resolver.swift +++ b/Sources/Resolver/Resolver.swift @@ -276,7 +276,7 @@ public final class Resolver { if let registrations = root.lookupAll(type) { return registrations.compactMap { reg in return reg.resolve(resolver: root, args: args) } } - fatalError("RESOLVER: '\(Service.self)' types not resolved.") + return [] } // Resolves and returns all named instances of the given Service type from the current registry or from its @@ -294,7 +294,7 @@ public final class Resolver { if let registrations = lookupAll(type) { return registrations.compactMap { reg in return reg.scope.resolve(registration: reg, resolver: self, args: args) } } - fatalError("RESOLVER: '\(Service.self)' types not resolved.") + return [] } /// Static function calls the root registry to resolve an optional Service type. @@ -367,7 +367,7 @@ public final class Resolver { /// Internal function searches the current and child registries for all ResolverRegistrations that matches /// the supplied type combind with their registered names. private final func lookupAllKeyed(_ type: Service.Type) -> [String: ResolverRegistration]? { - let key = Int(bitPattern: ObjectIdentifier(Service.self)) + let key = Int(bitPattern: ObjectIdentifier(type)) var result = namedRegistrations.filter { registration in return registration.key.hasPrefix("\(key):") }