diff --git a/Sources/Resolver/Resolver.swift b/Sources/Resolver/Resolver.swift index 975bbbe..2092bf9 100644 --- a/Sources/Resolver/Resolver.swift +++ b/Sources/Resolver/Resolver.swift @@ -261,6 +261,42 @@ 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.resolve(resolver: root, args: args) } + } + return [] + } + + // 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(registration: reg, resolver: self, args: args) } + } + return [] + } + /// 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 +355,31 @@ 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]? { + 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(type)) + var result = namedRegistrations.filter { registration in + return registration.key.hasPrefix("\(key):") + } + for child in childContainers { + guard let childRegistrations = child.lookupAllKeyed(type) else { + continue + } + result.merge(childRegistrations, uniquingKeysWith: { parentEntry, _ in return parentEntry }) + } + return result as? [String: ResolverRegistration] + } + /// 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 { diff --git a/Tests/ResolverTests/ResolverNameTests.swift b/Tests/ResolverTests/ResolverNameTests.swift index 88de734..024fb9b 100644 --- a/Tests/ResolverTests/ResolverNameTests.swift +++ b/Tests/ResolverTests/ResolverNameTests.swift @@ -183,4 +183,70 @@ class ResolverNameTests: XCTestCase { XCTAssert(barney?.name == "Barney") } + func testResolveAllOfType() { + + 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) + } + + 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) + } + + 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 { }