diff --git a/CHANGELOG b/CHANGELOG index 15dcf9c..f795570 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,5 +1,21 @@ # Resolver Changelog +### 1.4.4 + +* Reduced code size and improved performance +* Update registration cache key mechanism to prevent possible registration overwrites + +### 1.4.3 + +* Add capability for multiple child containers + +### 1.4.2 + +* Fix threading issue in LazyInjected and WeakLazyInjected property wrappers +* Fix argument passing in .implements +* Update projct for Xcode 12.5 +* Update Swift class deprecation + ### 1.4.1 * Fix bug forwarding new argument structure from factory to factory - PR#89 diff --git a/Documentation/Containers.md b/Documentation/Containers.md index d796f6d..a862102 100644 --- a/Documentation/Containers.md +++ b/Documentation/Containers.md @@ -43,9 +43,14 @@ extension Resolver: ResolverRegistering { } ``` -The static register and resolve functions simply pass the buck to main and root, respectively. +The static (class) register and resolve functions simply pass the buck to main and root, respectively. ```swift +public static func register(_ type: Service.Type = Service.self, name: Resolver.Name? = nil, + factory: @escaping ResolverFactoryArgumentsN) -> ResolverOptions { + return main.register(type, name: name, factory: factory) +} + public static func resolve(_ type: Service.Type = Service.self, name: String? = nil, args: Any? = nil) -> Service { return root.resolve(type, name: name, args: args) } @@ -172,3 +177,27 @@ Returning, we switch back and the app again behaves normally. Nice party trick, don't you think? +## Child Containers + +Resolver 1.4.3 adds support for multiple child containers. + +As stated above, you can put thousands of registrations into a single container but, should you desire to do so, you can now segment your registrations into smaller groups of containiners and then add each subcontainer to the main container. + +Consider... + +``` +extension Resolver { + static let containerA = Resolver() + static let containerB = Resolver() + + static func registerAllServices() { + main.add(child: containerA) + main.add(child: containerB) + ... + } +} +``` + +Now when main is asked to resolve a given service, it will first search its own registrations and then, if not found, will search each of the included child containers to see if one of them contains the needed registration. First match will return, and containers will be searched in the order in which they're added. + +This is basically a small change that reworks the "parent" mechanism to support multiple children. Parent (or "nested") containers still work as before. diff --git a/Documentation/Cycle.md b/Documentation/Cycle.md index 7da0738..6013f52 100644 --- a/Documentation/Cycle.md +++ b/Documentation/Cycle.md @@ -33,7 +33,7 @@ Note in particular the additional parameters needed to create a `XYZViewModel` a ## The Process -Let's kick things off by given a view controller its view model. +Let's kick things off by giving a view controller its view model. ```swift class MyViewController: UIViewController { diff --git a/Documentation/Registration.md b/Documentation/Registration.md index 7bc3e49..c8d99f9 100644 --- a/Documentation/Registration.md +++ b/Documentation/Registration.md @@ -2,7 +2,7 @@ ## Introduction -As mention in the introduction , in order for Resolve to *resolve* a request for a paticular service you first need to register a factory that knows how to instantiate an instance of the service. +As mentioned in the introduction, in order for Resolve to *resolve* a request for a paticular service you first need to register a factory that knows how to instantiate an instance of the service. ```swift Resolver.register { NetworkService() } @@ -28,7 +28,7 @@ Let's start by adding the master injection file for the entire application. Add a file named `AppDelegate+Injection.swift` to your project and add the following code: ```swift -#import Resolver +import Resolver extension Resolver: ResolverRegistering { public static func registerAllServices() { diff --git a/README.md b/README.md index 578ea93..318d9e9 100644 --- a/README.md +++ b/README.md @@ -127,11 +127,13 @@ Resolver is available under the MIT license. See the LICENSE file for more info. ## Additional Resouces +* [Resolver for iOS Dependency Injection: Getting Started | Ray Wenderlich](https://www.raywenderlich.com/22203552-resolver-for-ios-dependency-injection-getting-started) * [API Documentation](./Documentation/API/Classes/Resolver.html) * [Inversion of Control Design Pattern ~ Wikipedia](https://en.wikipedia.org/wiki/Inversion_of_control) * [Inversion of Control Containers and the Dependency Injection pattern ~ Martin Fowler](https://martinfowler.com/articles/injection.html) -* [Nuts and Bolts of Dependency Injection in Swift](https://cocoacasts.com/nuts-and-bolts-of-dependency-injection-in-swift/)\ +* [Nuts and Bolts of Dependency Injection in Swift](https://cocoacasts.com/nuts-and-bolts-of-dependency-injection-in-swift/) * [Dependency Injection in Swift](https://cocoacasts.com/dependency-injection-in-swift) * [SwinjectStoryboard](https://github.com/Swinject/SwinjectStoryboard) * [Swift 5.1 Takes Dependency Injection to the Next Level](https://medium.com/better-programming/taking-swift-dependency-injection-to-the-next-level-b71114c6a9c6) * [Builder Demo Application](https://github.com/hmlongco/Builder) + diff --git a/Resolver.podspec b/Resolver.podspec index 9d36564..7152250 100644 --- a/Resolver.podspec +++ b/Resolver.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = "Resolver" - s.version = "1.4.1" + s.version = "1.4.4" s.summary = "An ultralight Dependency Injection / Service Locator framework for Swift on iOS." s.homepage = "https://github.com/hmlongco/Resolver" s.license = "MIT" diff --git a/Resolver.xcodeproj/project.pbxproj b/Resolver.xcodeproj/project.pbxproj index 695ae67..07778d2 100644 --- a/Resolver.xcodeproj/project.pbxproj +++ b/Resolver.xcodeproj/project.pbxproj @@ -234,15 +234,15 @@ isa = PBXProject; attributes = { LastSwiftMigration = 9999; - LastUpgradeCheck = 1220; + LastUpgradeCheck = 1250; }; buildConfigurationList = OBJ_2 /* Build configuration list for PBXProject "Resolver" */; compatibilityVersion = "Xcode 3.2"; - developmentRegion = English; + developmentRegion = en; hasScannedForEncodings = 0; knownRegions = ( - English, en, + Base, ); mainGroup = OBJ_5; productRefGroup = OBJ_24 /* Products */; diff --git a/Resolver.xcodeproj/xcshareddata/xcschemes/Resolver-Package.xcscheme b/Resolver.xcodeproj/xcshareddata/xcschemes/Resolver-Package.xcscheme index ccd6d1c..ff0a007 100644 --- a/Resolver.xcodeproj/xcshareddata/xcschemes/Resolver-Package.xcscheme +++ b/Resolver.xcodeproj/xcshareddata/xcschemes/Resolver-Package.xcscheme @@ -1,6 +1,6 @@ ) -> ResolverOptions { lock.lock() defer { lock.unlock() } - let key = ObjectIdentifier(Service.self).hashValue - let registration = ResolverRegistrationOnly(resolver: self, key: key, name: name, factory: factory) + let key = Int(bitPattern: ObjectIdentifier(Service.self)) + let factory: ResolverFactoryAnyArguments = { (_,_) in factory() } + let registration = ResolverRegistration(resolver: self, key: key, name: name, factory: factory) add(registration: registration, with: key, name: name) - return registration + return ResolverOptions(registration: registration) } /// Registers a specifc Service type and its instantiating factory method. @@ -174,10 +186,11 @@ public final class Resolver { factory: @escaping ResolverFactoryResolver) -> ResolverOptions { lock.lock() defer { lock.unlock() } - let key = ObjectIdentifier(Service.self).hashValue - let registration = ResolverRegistrationResolver(resolver: self, key: key, name: name, factory: factory) + let key = Int(bitPattern: ObjectIdentifier(Service.self)) + let factory: ResolverFactoryAnyArguments = { (r,_) in factory(r) } + let registration = ResolverRegistration(resolver: self, key: key, name: name, factory: factory) add(registration: registration, with: key, name: name) - return registration + return ResolverOptions(registration: registration) } /// Registers a specifc Service type and its instantiating factory method with multiple argument support. @@ -193,10 +206,11 @@ public final class Resolver { factory: @escaping ResolverFactoryArgumentsN) -> ResolverOptions { lock.lock() defer { lock.unlock() } - let key = ObjectIdentifier(Service.self).hashValue - let registration = ResolverRegistrationArgumentsN(resolver: self, key: key, name: name, factory: factory) + let key = Int(bitPattern: ObjectIdentifier(Service.self)) + let factory: ResolverFactoryAnyArguments = { (r,a) in factory(r, Args(a)) } + let registration = ResolverRegistration(resolver: self, key: key, name: name, factory: factory ) add(registration: registration, with: key, name: name) - return registration + return ResolverOptions(registration: registration) } // MARK: - Service Resolution @@ -280,49 +294,54 @@ public final class Resolver { // MARK: - Internal - /// Internal function searches the current and parent registries for a ResolverRegistration that matches + /// Internal function searches the current and child registries for a ResolverRegistration that matches /// the supplied type and name. private final func lookup(_ type: Service.Type, name: Resolver.Name?) -> ResolverRegistration? { - let key = ObjectIdentifier(Service.self).hashValue - let containerName = name?.rawValue ?? NONAME - if let container = registrations[key], let registration = container[containerName] { - return registration as? ResolverRegistration - } - if let parent = parent, let registration = parent.lookup(type, name: name) { + let key = Int(bitPattern: ObjectIdentifier(Service.self)) + if let name = name?.rawValue { + if let registration = namedRegistrations["\(key):\(name)"] as? ResolverRegistration { + return registration + } + } else if let registration = typedRegistrations[key] as? ResolverRegistration { return registration } + for child in childContainers { + if let registration = child.lookup(type, name: name) { + return registration + } + } return nil } /// Internal function adds a new registration to the proper container. private final func add(registration: ResolverRegistration, with key: Int, name: Resolver.Name?) { - if var container = registrations[key] { - container[name?.rawValue ?? NONAME] = registration - registrations[key] = container + if let name = name?.rawValue { + namedRegistrations["\(key):\(name)"] = registration } else { - registrations[key] = [name?.rawValue ?? NONAME : registration] + typedRegistrations[key] = registration } } private let NONAME = "*" - private let parent: Resolver? private let lock = Resolver.lock - private var registrations = [Int : [String : Any]]() + private var childContainers: [Resolver] = [] + private var typedRegistrations = [Int : Any]() + private var namedRegistrations = [String : Any]() } /// Resolving an instance of a service is a recursive process (service A needs a B which needs a C). -private class ResolverRecursiveLock { +private final class ResolverRecursiveLock { init() { pthread_mutexattr_init(&recursiveMutexAttr) pthread_mutexattr_settype(&recursiveMutexAttr, PTHREAD_MUTEX_RECURSIVE) pthread_mutex_init(&recursiveMutex, &recursiveMutexAttr) } @inline(__always) - func lock() { + final func lock() { pthread_mutex_lock(&recursiveMutex) } @inline(__always) - func unlock() { + final func unlock() { pthread_mutex_unlock(&recursiveMutex) } private var recursiveMutex = pthread_mutex_t() @@ -330,7 +349,7 @@ private class ResolverRecursiveLock { } extension Resolver { - private static let lock = ResolverRecursiveLock() + fileprivate static let lock = ResolverRecursiveLock() } /// Resolver Service Name Space Support @@ -421,26 +440,16 @@ private func registrationCheck() { public typealias ResolverFactory = () -> Service? public typealias ResolverFactoryResolver = (_ resolver: Resolver) -> Service? public typealias ResolverFactoryArgumentsN = (_ resolver: Resolver, _ args: Resolver.Args) -> Service? +public typealias ResolverFactoryAnyArguments = (_ resolver: Resolver, _ args: Any?) -> Service? public typealias ResolverFactoryMutator = (_ resolver: Resolver, _ service: Service) -> Void public typealias ResolverFactoryMutatorArgumentsN = (_ resolver: Resolver, _ service: Service, _ args: Resolver.Args) -> Void /// A ResolverOptions instance is returned by a registration function in order to allow additonal configuratiom. (e.g. scopes, etc.) -public class ResolverOptions { +public struct ResolverOptions { // MARK: - Parameters - public var scope: ResolverScope - - fileprivate var mutator: ResolverFactoryMutator? - fileprivate var mutatorWithArgumentsN: ResolverFactoryMutatorArgumentsN? - fileprivate weak var resolver: Resolver? - - // MARK: - Lifecycle - - public init(resolver: Resolver) { - self.resolver = resolver - self.scope = Resolver.defaultScope - } + internal var registration: ResolverRegistration // MARK: - Fuctionality @@ -453,8 +462,8 @@ public class ResolverOptions { /// - returns: ResolverOptions instance that allows further customization of registered Service. /// @discardableResult - public final func implements(_ type: Protocol.Type, name: Resolver.Name? = nil) -> ResolverOptions { - resolver?.register(type.self, name: name) { r, _ in r.resolve(Service.self) as? Protocol } + public func implements(_ type: Protocol.Type, name: Resolver.Name? = nil) -> ResolverOptions { + registration.resolver?.register(type.self, name: name) { r, args in r.resolve(Service.self, args: args) as? Protocol } return self } @@ -465,8 +474,10 @@ public class ResolverOptions { /// - returns: ResolverOptions instance that allows further customization of registered Service. /// @discardableResult - public final func resolveProperties(_ block: @escaping ResolverFactoryMutator) -> ResolverOptions { - mutator = block + public func resolveProperties(_ block: @escaping ResolverFactoryMutator) -> ResolverOptions { + registration.mutator = { (_ resolver: Resolver, _ service: Service, _ args: Resolver.Args) in + block(resolver, service) + } return self } @@ -477,8 +488,8 @@ public class ResolverOptions { /// - returns: ResolverOptions instance that allows further customization of registered Service. /// @discardableResult - public final func resolveProperties(_ block: @escaping ResolverFactoryMutatorArgumentsN) -> ResolverOptions { - mutatorWithArgumentsN = block + public func resolveProperties(_ block: @escaping ResolverFactoryMutatorArgumentsN) -> ResolverOptions { + registration.mutator = block return self } @@ -489,103 +500,49 @@ public class ResolverOptions { /// - returns: ResolverOptions instance that allows further customization of registered Service. /// @discardableResult - public final func scope(_ scope: ResolverScope) -> ResolverOptions { - self.scope = scope + public func scope(_ scope: ResolverScope) -> ResolverOptions { + registration.scope = scope return self } - - /// Internal function to apply mutations with and w/o arguments - fileprivate func mutate(_ service: Service, resolver: Resolver, args: Any?) { - self.mutator?(resolver, service) - if let mutatorWithArgumentsN = mutatorWithArgumentsN { - mutatorWithArgumentsN(resolver, service, Resolver.Args(args)) - } - } } -/// ResolverRegistration base class stores the registration keys. -public class ResolverRegistration: ResolverOptions { +/// ResolverRegistration base class provides storage for the registration keys, scope, and property mutator. +public final class ResolverRegistration { + + fileprivate let key: Int + fileprivate let cacheKey: String + fileprivate let factory: ResolverFactoryAnyArguments - public var key: Int - public var cacheKey: String + fileprivate var scope: ResolverScope = Resolver.defaultScope + fileprivate var mutator: ResolverFactoryMutatorArgumentsN? + + fileprivate weak var resolver: Resolver? - public init(resolver: Resolver, key: Int, name: Resolver.Name?) { + public init(resolver: Resolver, key: Int, name: Resolver.Name?, factory: @escaping ResolverFactoryAnyArguments) { + self.resolver = resolver self.key = key if let namedService = name { self.cacheKey = String(key) + ":" + namedService.rawValue } else { self.cacheKey = String(key) } - super.init(resolver: resolver) - } - - public func resolve(resolver: Resolver, args: Any?) -> Service? { - fatalError("abstract function") - } - -} - -/// ResolverRegistration stores a service definition and its factory closure. -public final class ResolverRegistrationOnly: ResolverRegistration { - - public var factory: ResolverFactory - - public init(resolver: Resolver, key: Int, name: Resolver.Name?, factory: @escaping ResolverFactory) { self.factory = factory - super.init(resolver: resolver, key: key, name: name) } - public final override func resolve(resolver: Resolver, args: Any?) -> Service? { - guard let service = factory() else { + public final func resolve(resolver: Resolver, args: Any?) -> Service? { + guard let service = factory(resolver, args) else { return nil } - mutate(service, resolver: resolver, args: args) + self.mutator?(resolver, service, Resolver.Args(args)) return service } -} - -/// ResolverRegistrationResolver stores a service definition and its factory closure. -public final class ResolverRegistrationResolver: ResolverRegistration { - - public var factory: ResolverFactoryResolver - - public init(resolver: Resolver, key: Int, name: Resolver.Name?, factory: @escaping ResolverFactoryResolver) { - self.factory = factory - super.init(resolver: resolver, key: key, name: name) - } - - public final override func resolve(resolver: Resolver, args: Any?) -> Service? { - guard let service = factory(resolver) else { - return nil - } - mutate(service, resolver: resolver, args: args) - return service - } -} - -/// ResolverRegistrationArguments stores a service definition and its factory closure. -public final class ResolverRegistrationArgumentsN: ResolverRegistration { - - public var factory: ResolverFactoryArgumentsN - - public init(resolver: Resolver, key: Int, name: Resolver.Name?, factory: @escaping ResolverFactoryArgumentsN) { - self.factory = factory - super.init(resolver: resolver, key: key, name: name) - } - public final override func resolve(resolver: Resolver, args: Any?) -> Service? { - guard let service = factory(resolver, Resolver.Args(args)) else { - return nil - } - mutate(service, resolver: resolver, args: args) - return service - } } // Scopes /// Resolver scopes exist to control when resolution occurs and how resolved instances are cached. (If at all.) -public protocol ResolverScopeType: class { +public protocol ResolverScopeType: AnyObject { func resolve(resolver: Resolver, registration: ResolverRegistration, args: Any?) -> Service? } @@ -785,6 +742,7 @@ public extension UIViewController { /// Wrapped dependent service is not resolved until service is accessed. /// @propertyWrapper public struct LazyInjected { + private var lock = Resolver.lock private var initialize: Bool = true private var service: Service! public var container: Resolver? @@ -796,23 +754,34 @@ public extension UIViewController { self.container = container } public var isEmpty: Bool { + lock.lock() + defer { lock.unlock() } return service == nil } public var wrappedValue: Service { mutating get { + lock.lock() + defer { lock.unlock() } if initialize { self.initialize = false self.service = container?.resolve(Service.self, name: name, args: args) ?? Resolver.resolve(Service.self, name: name, args: args) } return service } - mutating set { service = newValue } + mutating set { + lock.lock() + defer { lock.unlock() } + initialize = false + service = newValue + } } public var projectedValue: LazyInjected { get { return self } mutating set { self = newValue } } public mutating func release() { + lock.lock() + defer { lock.unlock() } self.service = nil } } @@ -822,6 +791,7 @@ public extension UIViewController { /// Wrapped dependent service is not resolved until service is accessed. /// @propertyWrapper public struct WeakLazyInjected { + private var lock = Resolver.lock private var initialize: Bool = true private weak var service: AnyObject? public var container: Resolver? @@ -833,19 +803,27 @@ public extension UIViewController { self.container = container } public var isEmpty: Bool { + lock.lock() + defer { lock.unlock() } return service == nil } public var wrappedValue: Service? { mutating get { + lock.lock() + defer { lock.unlock() } if initialize { self.initialize = false - let service = container?.resolve(Service.self, name: name, args: args) ?? Resolver.resolve(Service.self, name: name, args: args) - self.service = service as AnyObject - return service + self.service = (container?.resolve(Service.self, name: name, args: args) + ?? Resolver.resolve(Service.self, name: name, args: args)) as AnyObject } return service as? Service } - mutating set { service = newValue as AnyObject } + mutating set { + lock.lock() + defer { lock.unlock() } + initialize = false + service = newValue as AnyObject + } } public var projectedValue: WeakLazyInjected { get { return self } diff --git a/Tests/ResolverTests/ResolverArgumentTests.swift b/Tests/ResolverTests/ResolverArgumentTests.swift index e45aa11..bbdbc71 100644 --- a/Tests/ResolverTests/ResolverArgumentTests.swift +++ b/Tests/ResolverTests/ResolverArgumentTests.swift @@ -149,5 +149,31 @@ class ResolverArgumentTests: XCTestCase { XCTAssert(service?.string == "Barney") } + func testGetSingleArgumentPassingInImplements() { + resolver.register { (r, arg) -> XYZArgumentService in + XCTAssert(arg.get()) + return XYZArgumentService(condition: arg.get()) + } + .implements(XYZArgumentProtocol.self) + let service: XYZArgumentProtocol? = resolver.optional(args: true) + XCTAssertNotNil(service) + XCTAssert(service?.condition == true) + XCTAssert(service?.string == "Barney") + } + + func testGetKeyedArgumentPassingInImplements() { + resolver.register { (r, args) -> XYZArgumentService in + let condition: Bool = args.get("condition") + XCTAssert(condition) + let string: String = args.get("name") + XCTAssert(string == "Fred") + return XYZArgumentService(condition: condition, string: string) + } + .implements(XYZArgumentProtocol.self) + let service: XYZArgumentProtocol? = resolver.optional(args: ["condition": true, "name": "Fred"]) + XCTAssertNotNil(service) + XCTAssert(service?.condition == true) + XCTAssert(service?.string == "Fred") + } } diff --git a/Tests/ResolverTests/ResolverContainerTests.swift b/Tests/ResolverTests/ResolverContainerTests.swift index e044580..91a000e 100644 --- a/Tests/ResolverTests/ResolverContainerTests.swift +++ b/Tests/ResolverTests/ResolverContainerTests.swift @@ -13,6 +13,7 @@ class ResolverContainerTests: XCTestCase { var resolver1: Resolver! var resolver2: Resolver! + var resolver3: Resolver! override func setUp() { super.setUp() @@ -71,6 +72,32 @@ class ResolverContainerTests: XCTestCase { XCTAssertNotNil(r2) XCTAssert(r2?.name == "Resolver 1") } + + func testResolverChildContainers() { + + resolver1 = Resolver() + resolver2 = Resolver() + resolver3 = Resolver() + + let root: Resolver! = resolver1 + + root?.add(child: resolver2) + root?.add(child: resolver3) + + resolver1.register() { XYZSessionService() } + resolver2.register() { XYZNameService("Resolver 2") } + resolver3.register() { XYZService(root.optional()) } + + // should find in resolver in which it was defined + let nameService: XYZNameService? = root.optional() + XCTAssertNotNil(nameService) + XCTAssert(nameService?.name == "Resolver 2") + + // should resolve child and then find + let service: XYZService? = root.optional() + XCTAssertNotNil(service) + XCTAssertNotNil(service?.session) + } func testResolverParentContainerOverride() { diff --git a/Tests/ResolverTests/ResolverNameTests.swift b/Tests/ResolverTests/ResolverNameTests.swift index 4c4e291..b7978ed 100644 --- a/Tests/ResolverTests/ResolverNameTests.swift +++ b/Tests/ResolverTests/ResolverNameTests.swift @@ -139,5 +139,29 @@ class ResolverNameTests: XCTestCase { XCTAssert(fred?.name == "Fred") } + + struct AppConfig { + let host1 = "https://www.amazon.com" + let host2 = "https://www.google.com" + } + + func testResolverNamedStringRegististrations() { + + Task.init { + + } + + resolver.register { AppConfig() } + resolver.register(name: "host1") { r in r.resolve(AppConfig.self).host1 } + resolver.register(name: "host2") { r in r.resolve(AppConfig.self).host2 } + + let host1: String = resolver.resolve(name: "host1") + let host2: String = resolver.resolve(name: "host2") + let host3: String? = resolver.optional(name: "host3") + + XCTAssert(host1 == "https://www.amazon.com") + XCTAssert(host2 == "https://www.google.com") + XCTAssertNil(host3) + } } diff --git a/Tests/ResolverTests/ResolverScopeReferenceTests.swift b/Tests/ResolverTests/ResolverScopeReferenceTests.swift index da80cd4..8ee8191 100644 --- a/Tests/ResolverTests/ResolverScopeReferenceTests.swift +++ b/Tests/ResolverTests/ResolverScopeReferenceTests.swift @@ -99,6 +99,49 @@ class ResolverScopeReferenceTests: XCTestCase { } } + func testResolverScopeCachedImplements() { + // Reset... + ResolverScope.cached.reset() + resolver.register { XYZSessionService() } + .implements(XYZSessionProtocol.self) + .scope(.cached) + let service1: XYZSessionService? = resolver.optional() + let service2: XYZSessionService? = resolver.optional() + XCTAssertNotNil(service1) + XCTAssertNotNil(service2) + // Also test if "implements" protocol resolves + let service3: XYZSessionProtocol? = resolver.optional() + XCTAssertNotNil(service3) + // check identity + var originalID: UUID? + if let s1 = service1, let s2 = service2, let s3 = service3 { + XCTAssert(s1.id == s2.id) + XCTAssert(s2.id == s3.id) + originalID = s1.id + } else { + XCTFail("sessions not cached") + } + // Reset... + ResolverScope.cached.reset() + // ...and try again + var newUUID: UUID? + if let newService: XYZSessionService = resolver.optional(), let originalID = originalID { + XCTAssert(originalID != newService.id) + newUUID = newService.id + } else { + XCTFail("newService not resolved") + } + // Reset... + ResolverScope.cached.reset() + // ...and try once more with protocol + if let newService: XYZSessionProtocol = resolver.optional(), let originalID = originalID, let newID = newUUID { + XCTAssert(originalID != newService.id) + XCTAssert(newID != newService.id) + } else { + XCTFail("newService not resolved") + } + } + func testResolverScopeUnique() { resolver.register { XYZSessionService() }.scope(.unique) let service1: XYZSessionService? = resolver.optional() diff --git a/Tests/ResolverTests/TestData.swift b/Tests/ResolverTests/TestData.swift index 3db2546..ead4bc1 100644 --- a/Tests/ResolverTests/TestData.swift +++ b/Tests/ResolverTests/TestData.swift @@ -101,7 +101,13 @@ class XYZValueService { } } -class XYZArgumentService { + +protocol XYZArgumentProtocol { + var condition: Bool { get } + var string: String { get } +} + +class XYZArgumentService: XYZArgumentProtocol { var condition: Bool var string: String init(condition: Bool = false, string: String = "Barney") { diff --git a/Tests/ResolverTests/XCTestManifests.swift b/Tests/ResolverTests/XCTestManifests.swift index d1f280f..502d5b7 100644 --- a/Tests/ResolverTests/XCTestManifests.swift +++ b/Tests/ResolverTests/XCTestManifests.swift @@ -6,9 +6,11 @@ extension ResolverArgumentTests { // `swift test --generate-linuxmain` // to regenerate. static let __allTests__ResolverArgumentTests = [ + ("testGetKeyedArgumentPassingInImplements", testGetKeyedArgumentPassingInImplements), ("testGetKeyedArguments", testGetKeyedArguments), ("testGetPropertiesKeyedArguments", testGetPropertiesKeyedArguments), ("testGetSingleArgument", testGetSingleArgument), + ("testGetSingleArgumentPassingInImplements", testGetSingleArgumentPassingInImplements), ("testKeyedArgumentsFunction", testKeyedArgumentsFunction), ("testOptionalArgument", testOptionalArgument), ("testPropertiesGetSingleArgument", testPropertiesGetSingleArgument), @@ -150,6 +152,7 @@ extension ResolverScopeReferenceTests { static let __allTests__ResolverScopeReferenceTests = [ ("testResolverScopeApplication", testResolverScopeApplication), ("testResolverScopeCached", testResolverScopeCached), + ("testResolverScopeCachedImplements", testResolverScopeCachedImplements), ("testResolverScopeGraph", testResolverScopeGraph), ("testResolverScopeShared", testResolverScopeShared), ("testResolverScopeUnique", testResolverScopeUnique),