diff --git a/Sources/InfomaniakDI/SimpleResolver.swift b/Sources/InfomaniakDI/SimpleResolver.swift index 5e30a2f..0d3fd5e 100644 --- a/Sources/InfomaniakDI/SimpleResolver.swift +++ b/Sources/InfomaniakDI/SimpleResolver.swift @@ -65,6 +65,7 @@ public final class SimpleResolver: SimpleResolvable, SimpleStorable, CustomDebug enum ErrorDomain: Error { case factoryMissing(identifier: String) case typeMissmatch(expected: String, got: String) + case unknown } /// One singleton to rule them all @@ -76,8 +77,14 @@ public final class SimpleResolver: SimpleResolvable, SimpleStorable, CustomDebug /// Resolved object collection var store = [String: Any]() + private var queueSpecificKey: DispatchSpecificKey = DispatchSpecificKey() + /// A serial queue for thread safety - private let queue = DispatchQueue(label: "com.infomaniakDI.resolver") + private lazy var queue: DispatchQueue = { + let serialQueue = DispatchQueue(label: "com.infomaniakDI.resolver") + serialQueue.setSpecific(key: self.queueSpecificKey, value: serialQueue) + return serialQueue + }() // MARK: SimpleStorable @@ -99,36 +106,65 @@ public final class SimpleResolver: SimpleResolvable, SimpleStorable, CustomDebug resolver: SimpleResolvable) throws -> Service { let serviceIdentifier = buildIdentifier(type: type, forIdentifier: customIdentifier) - // load form store - var fetchedService: Any? - queue.sync { - fetchedService = store[serviceIdentifier] - } - if let service = fetchedService as? Service { - return service + guard notOnInternalQueue else { + return try loadOrResolve( + serviceIdentifier: serviceIdentifier, + factoryParameters: factoryParameters, + resolver: resolver + ) } - // load service from factory - var factory: Factoryable? + var resolvedService: Service? + var resolveError: Error? queue.sync { - factory = factories[serviceIdentifier] - } - guard let factory = factory else { - throw ErrorDomain.factoryMissing(identifier: serviceIdentifier) + do { + resolvedService = try loadOrResolve( + serviceIdentifier: serviceIdentifier, + factoryParameters: factoryParameters, + resolver: resolver + ) + } catch { + resolveError = error + } } - // Apply factory closure - let builtType = try factory.build(factoryParameters: factoryParameters, resolver: resolver) - guard let service = builtType as? Service else { - throw ErrorDomain.typeMissmatch(expected: "\(Service.Type.self)", got: "\(builtType.self)") + guard let resolvedService else { + guard let resolveError else { + throw ErrorDomain.unknown + } + throw resolveError } - // keep in store built object for later - queue.sync { + return resolvedService + } + + private func loadOrResolve(serviceIdentifier: String, + factoryParameters: [String: Any]?, + resolver: SimpleResolvable) throws -> Service { + if let fetchedObject = store[serviceIdentifier], + let fetchedService = fetchedObject as? Service { + return fetchedService + } else { + guard let factory = factories[serviceIdentifier] else { + throw ErrorDomain.factoryMissing(identifier: serviceIdentifier) + } + + let builtType = try factory.build(factoryParameters: factoryParameters, resolver: resolver) + guard let service = builtType as? Service else { + throw ErrorDomain.typeMissmatch(expected: "\(Service.Type.self)", got: "\(builtType.self)") + } + store[serviceIdentifier] = service + return service } + } - return service + private var notOnInternalQueue: Bool { + if DispatchQueue.getSpecific(key: queueSpecificKey) != nil { + return false + } else { + return true + } } // MARK: internal