Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Ability to resolve multiple implementations of same type #154

Open
wants to merge 9 commits into
base: master
Choose a base branch
from
61 changes: 61 additions & 0 deletions Sources/Resolver/Resolver.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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<Service>(_ 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<Service>(_ 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.
Expand Down Expand Up @@ -319,6 +355,31 @@ public final class Resolver {
return nil
}

/// Internal function searches the current and child registries for all ResolverRegistration<Service>s that matches
/// the supplied type.
private final func lookupAll<Service>(_ type: Service.Type) -> [ResolverRegistration<Service>]? {
guard let values = lookupAllKeyed(type)?.values else {
return nil
}
return Array(values)
}

/// Internal function searches the current and child registries for all ResolverRegistration<Service>s that matches
/// the supplied type combind with their registered names.
private final func lookupAllKeyed<Service>(_ type: Service.Type) -> [String: ResolverRegistration<Service>]? {
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<Service>]
}

/// Internal function adds a new registration to the proper container.
private final func add<Service>(registration: ResolverRegistration<Service>, with key: Int, name: Resolver.Name?) {
if let name = name?.rawValue {
Expand Down
66 changes: 66 additions & 0 deletions Tests/ResolverTests/ResolverNameTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
}
10 changes: 9 additions & 1 deletion Tests/ResolverTests/TestData.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
}
Expand Down