From 0904ef993a4c19d6ff9ee6f2a318d0392d909c00 Mon Sep 17 00:00:00 2001 From: Alex Skorulis Date: Mon, 20 Oct 2025 15:06:54 +1100 Subject: [PATCH] Remove Lazy and Provider wrapper types --- Sources/Swinject/InstanceWrapper.swift | 55 ---- Tests/SwinjectTests/Circularity.swift | 32 -- .../SwinjectTests/ContainerTests.Speed.swift | 2 - Tests/SwinjectTests/EmploymentAssembly.swift | 80 ----- Tests/SwinjectTests/LazyTests.swift | 111 ------- Tests/SwinjectTests/ProviderTests.swift | 110 ------- Tests/SwinjectTests/SynchronizedTests.swift | 282 ------------------ 7 files changed, 672 deletions(-) delete mode 100644 Tests/SwinjectTests/EmploymentAssembly.swift delete mode 100644 Tests/SwinjectTests/LazyTests.swift delete mode 100644 Tests/SwinjectTests/ProviderTests.swift delete mode 100644 Tests/SwinjectTests/SynchronizedTests.swift diff --git a/Sources/Swinject/InstanceWrapper.swift b/Sources/Swinject/InstanceWrapper.swift index 3efe28e3..50a073e7 100644 --- a/Sources/Swinject/InstanceWrapper.swift +++ b/Sources/Swinject/InstanceWrapper.swift @@ -7,61 +7,6 @@ protocol InstanceWrapper { init?(inContainer container: SwinjectContainer, withInstanceFactory factory: ((GraphIdentifier?) -> Any?)?) } -/// Wrapper to enable delayed dependency instantiation. -/// `Lazy` does not need to be explicitly registered into the ``Container`` - resolution will work -/// as long as there is a registration for the `Type`. -public final class Lazy: InstanceWrapper { - static var wrappedType: Any.Type { return Service.self } - - private let factory: (GraphIdentifier?) -> Any? - private let graphIdentifier: GraphIdentifier? - private weak var container: SwinjectContainer? - - init?(inContainer container: SwinjectContainer, withInstanceFactory factory: ((GraphIdentifier?) -> Any?)?) { - guard let factory = factory else { return nil } - self.factory = factory - graphIdentifier = container.currentObjectGraph - self.container = container - } - - private var _instance: Service? - - /// Getter for the wrapped object. - /// It will be resolved from the ``Container`` when first accessed, all other calls will return the same instance. - public var instance: Service { - if let instance = _instance { - return instance - } else { - _instance = makeInstance() - return _instance! - } - } - - private func makeInstance() -> Service? { - factory(graphIdentifier) as? Service - } -} - -/// Wrapper to enable delayed dependency instantiation. -/// `Provider` does not need to be explicitly registered into the ``Container`` - resolution will work -/// as long as there is a registration for the `Type`. -public final class Provider: InstanceWrapper { - static var wrappedType: Any.Type { return Service.self } - - private let factory: (GraphIdentifier?) -> Any? - - init?(inContainer _: SwinjectContainer, withInstanceFactory factory: ((GraphIdentifier?) -> Any?)?) { - guard let factory = factory else { return nil } - self.factory = factory - } - - /// Getter for the wrapped object. - /// New instance will be resolved from the ``Container`` every time it is accessed. - public var instance: Service { - return factory(.none) as! Service - } -} - extension Optional: InstanceWrapper { static var wrappedType: Any.Type { return Wrapped.self } diff --git a/Tests/SwinjectTests/Circularity.swift b/Tests/SwinjectTests/Circularity.swift index 8a198237..b282ac7a 100644 --- a/Tests/SwinjectTests/Circularity.swift +++ b/Tests/SwinjectTests/Circularity.swift @@ -70,35 +70,3 @@ internal class DDependingOnBC: D { internal class CDependingOnWeakB: C { weak var b: B? } - -internal protocol LazyParentProtocol { - var child1: LazyChildProtocol { get } - var child2: LazyChildProtocol { get } -} -internal protocol LazyChildProtocol: AnyObject { - var lazy: Lazy { get } -} -internal protocol LazySingletonProtocol { - var lazy: Lazy { get } -} -internal protocol LazilyResolvedProtocol: AnyObject { } - -internal class LazyParent: LazyParentProtocol { - let child1: LazyChildProtocol - let child2: LazyChildProtocol - - init(child1: LazyChildProtocol, child2: LazyChildProtocol) { - self.child1 = child1 - self.child2 = child2 - } -} - -internal class LazyChild: LazyChildProtocol, LazySingletonProtocol { - let lazy: Lazy - - init(lazy: Lazy) { - self.lazy = lazy - } -} - -internal class LazilyResolved: LazilyResolvedProtocol { } diff --git a/Tests/SwinjectTests/ContainerTests.Speed.swift b/Tests/SwinjectTests/ContainerTests.Speed.swift index 592a6940..70491aba 100644 --- a/Tests/SwinjectTests/ContainerTests.Speed.swift +++ b/Tests/SwinjectTests/ContainerTests.Speed.swift @@ -45,8 +45,6 @@ fileprivate extension SwinjectContainer { for _ in 0..<500_000 { _ = resolve(Animal.self) as? Cat _ = resolve(Animal.self, argument: "Mimi") as? Cat - let lazy = resolve(Lazy.self, arguments: "Mew", true) - _ = lazy?.instance as? Cat } } } diff --git a/Tests/SwinjectTests/EmploymentAssembly.swift b/Tests/SwinjectTests/EmploymentAssembly.swift deleted file mode 100644 index fa549de6..00000000 --- a/Tests/SwinjectTests/EmploymentAssembly.swift +++ /dev/null @@ -1,80 +0,0 @@ -// -// Copyright © 2019 Swinject Contributors. All rights reserved. -// - -import Swinject - -class Customer {} - -class Employee { - let customer: Customer - let lazyCustomer: Lazy - let providedCustomer: Provider - var employer: Employer? - - init(customer: Customer, lazyCustomer: Lazy, providedCustomer: Provider) { - self.customer = customer - self.lazyCustomer = lazyCustomer - self.providedCustomer = providedCustomer - } -} - -class Employer { - let customer: Customer - let lazyCustomer: Lazy - let providedCustomer: Provider - let employee: Employee - let lazyEmployee: Lazy - let providedEmployee: Provider - - init( - customer: Customer, - lazyCustomer: Lazy, - providedCustomer: Provider, - employee: Employee, - lazyEmployee: Lazy, - providedEmployee: Provider - ) { - self.customer = customer - self.lazyCustomer = lazyCustomer - self.providedCustomer = providedCustomer - self.employee = employee - self.lazyEmployee = lazyEmployee - self.providedEmployee = providedEmployee - } -} - -final class EmploymentAssembly { - private let scope: ObjectScope - - init(scope: ObjectScope) { - self.scope = scope - } - - func assemble(container: SwinjectContainer) { - container.register(Customer.self) { _ in Customer() }.inObjectScope(scope) - - container.register(Employee.self) { - Employee( - customer: $0.resolve(Customer.self)!, - lazyCustomer: $0.resolve(Lazy.self)!, - providedCustomer: $0.resolve(Provider.self)! - ) - }.initCompleted { - if self.scope !== ObjectScope.transient { - $1.employer = $0.resolve(Employer.self) - } - }.inObjectScope(scope) - - container.register(Employer.self) { - Employer( - customer: $0.resolve(Customer.self)!, - lazyCustomer: $0.resolve(Lazy.self)!, - providedCustomer: $0.resolve(Provider.self)!, - employee: $0.resolve(Employee.self)!, - lazyEmployee: $0.resolve(Lazy.self)!, - providedEmployee: $0.resolve(Provider.self)! - ) - }.inObjectScope(scope) - } -} diff --git a/Tests/SwinjectTests/LazyTests.swift b/Tests/SwinjectTests/LazyTests.swift deleted file mode 100644 index dcb9ec69..00000000 --- a/Tests/SwinjectTests/LazyTests.swift +++ /dev/null @@ -1,111 +0,0 @@ -// -// Copyright © 2021 Swinject Contributors. All rights reserved. -// - -import XCTest -import Swinject - -class LazyTests: XCTestCase { - var container: SwinjectContainer! - - override func setUpWithError() throws { - container = SwinjectContainer() - } - - // MARK: Instance production - - func testContainerProvidesInstanceFromContainer() { - container.register(Animal.self) { _ in Dog() } - let lazy = container.resolve(Lazy.self) - XCTAssert(lazy?.instance is Dog) - } - - func testContainerDoesNotCreateInstanceUntilRequested() { - var created = false - container.register(Animal.self) { _ in created = true; return Dog() } - - _ = container.resolve(Lazy.self) - - XCTAssertFalse(created) - } - - func testContainerResolveInstanceFromContainerOnlyOnce() { - var created = 0 - container.register(Animal.self) { _ in created += 1; return Dog() } - - let lazy = container.resolve(Lazy.self) - _ = lazy?.instance - _ = lazy?.instance - - XCTAssertEqual(created, 1) - } - - func testContainerDoesNotResolveLazyIfBaseTypeIsNotRegistered() { - let lazy = container.resolve(Lazy.self) - XCTAssertNil(lazy) - } - - // MARK: Object scopes - - func testContainerAlwaysProducesDifferentInstanceForRelatedObjectsInTransientScope() { - EmploymentAssembly(scope: .transient).assemble(container: container) - let employer = container.resolve(Employer.self)! - XCTAssert(employer.lazyCustomer.instance !== employer.employee.lazyCustomer.instance) - XCTAssert(employer.lazyCustomer.instance !== employer.customer) - } - - func testContainerAlwaysProducesTheSameInstanceForRelatedObjectsInContainerScope() { - EmploymentAssembly(scope: .container).assemble(container: container) - let employer = container.resolve(Employer.self)! - XCTAssert(employer.lazyCustomer.instance === employer.employee.lazyCustomer.instance) - XCTAssert(employer.lazyCustomer.instance === employer.customer) - } - - func testContainerAlwaysProducesTheSameInstanceForRelatedObjectsInGraphScope() { - EmploymentAssembly(scope: .graph).assemble(container: container) - let employer = container.resolve(Employer.self)! - XCTAssert(employer.lazyCustomer.instance === employer.employee.lazyCustomer.instance) - XCTAssert(employer.lazyCustomer.instance === employer.customer) - } - - // MARK: Complex registrations - - func testContainerResolvesLazyWithArguments() { - container.register(Dog.self) { (_, name, _: Int) in Dog(name: name) } - let lazy = container.resolve(Lazy.self, arguments: "Hachi", 42) - XCTAssertEqual(lazy?.instance.name, "Hachi") - } - - func testContainerResolvesLazyWithName() { - container.register(Dog.self, name: "Hachi") { _ in Dog() } - let lazy = container.resolve(Lazy.self, name: "Hachi") - XCTAssertNotNil(lazy) - } - - func testContainerDoesNotResolveLazyWithWrongName() { - container.register(Dog.self, name: "Hachi") { _ in Dog() } - let lazy = container.resolve(Lazy.self, name: "Mimi") - XCTAssertNil(lazy) - } - - func testContainerDoesResolveForwardedLazyType() { - container.register(Dog.self) { _ in Dog() }.implements(Animal.self) - let lazy = container.resolve(Lazy.self) - XCTAssertNotNil(lazy) - } - - // MARK: Circular dependencies - - func testContainerResolvesDependenciesToSameInstance() { - EmploymentAssembly(scope: .graph).assemble(container: container) - let employer = container.resolve(Employer.self) - XCTAssert(employer?.employee.employer === employer) - XCTAssert(employer?.lazyEmployee.instance.employer === employer) - } - - func testContainerResolvesCircularDependenciesForLazyInstance() { - EmploymentAssembly(scope: .graph).assemble(container: container) - let employee = container.resolve(Lazy.self) - XCTAssertNotNil(employee?.instance.employer) - } -} diff --git a/Tests/SwinjectTests/ProviderTests.swift b/Tests/SwinjectTests/ProviderTests.swift deleted file mode 100644 index 879c583a..00000000 --- a/Tests/SwinjectTests/ProviderTests.swift +++ /dev/null @@ -1,110 +0,0 @@ -// -// Copyright © 2021 Swinject Contributors. All rights reserved. -// - -import XCTest -@testable import Swinject - -class ProviderTests: XCTestCase { - var container: SwinjectContainer! - - override func setUpWithError() throws { - container = SwinjectContainer() - } - - // MARK: Instance production - - func testProviderProvidesInstanceFromContainer() { - container.register(Animal.self) { _ in Dog() } - let provider = container.resolve(Provider.self) - XCTAssert(provider?.instance is Dog) - } - - func testProviderDoesNotCreateInstanceUntilRequested() { - var created = false - container.register(Animal.self) { _ in created = true; return Dog() } - - _ = container.resolve(Provider.self) - - XCTAssertFalse(created) - } - - func testProviderResolveInstanceFromContainerEachTime() { - var created = 0 - container.register(Animal.self) { _ in created += 1; return Dog() } - - let provider = container.resolve(Provider.self) - _ = provider?.instance - _ = provider?.instance - - XCTAssertEqual(created, 2) - } - - func testProviderDoesNotResolveProviderIfBaseTypeIsNotRegistered() { - let provider = container.resolve(Provider.self) - XCTAssertNil(provider) - } - - // MARK: Object scopes - - func testContainerAlwaysProducesDifferentIncetanceInTransientScope() { - EmploymentAssembly(scope: .transient).assemble(container: container) - let employer = container.resolve(Employer.self)! - XCTAssert(employer.providedEmployee.instance !== employer.providedEmployee.instance) - XCTAssert(employer.employee.providedCustomer.instance !== employer.providedCustomer.instance) - } - - func testContainerAlwaysProducesDifferentIncetanceInGraphScope() { - EmploymentAssembly(scope: .graph).assemble(container: container) - let employer = container.resolve(Employer.self)! - XCTAssert(employer.providedEmployee.instance !== employer.providedEmployee.instance) - XCTAssert(employer.employee.providedCustomer.instance !== employer.providedCustomer.instance) - } - - func testContainerAlwaysProducesSameIncetanceInContainerScope() { - EmploymentAssembly(scope: .container).assemble(container: container) - let employer = container.resolve(Employer.self)! - XCTAssert(employer.providedEmployee.instance === employer.providedEmployee.instance) - XCTAssert(employer.employee.providedCustomer.instance === employer.providedCustomer.instance) - } - - // MARK: Complex registrations - - func testContainerResolvesProviderrWithArguments() { - container.register(Dog.self) { (_, name, _: Int) in Dog(name: name) } - let provider = container.resolve(Provider.self, arguments: "Hachi", 42) - XCTAssertEqual(provider?.instance.name, "Hachi") - } - - func testContainerResolvesProviderWithName() { - container.register(Dog.self, name: "Hachi") { _ in Dog() } - let provider = container.resolve(Provider.self, name: "Hachi") - XCTAssertNotNil(provider) - } - - func testContainerDoesNotResolveProoviderWithWrongName() { - container.register(Dog.self, name: "Hachi") { _ in Dog() } - let provider = container.resolve(Provider.self, name: "Mimi") - XCTAssertNil(provider) - } - - func testContainerResolveForwarrdedProviderType() { - container.register(Dog.self) { _ in Dog() }.implements(Animal.self) - let provider = container.resolve(Provider.self) - XCTAssertNotNil(provider) - } - - // MARK: Circular dependencies - - func testContainerResolvesNonProovidedDependenciesToTheSameInstance() { - EmploymentAssembly(scope: .graph).assemble(container: container) - let employer = container.resolve(Provider.self)?.instance - XCTAssert(employer?.employee.employer === employer) - } - - func testContainerResolvesProvidedDependenciesToDifferentInstances() { - EmploymentAssembly(scope: .graph).assemble(container: container) - let employer = container.resolve(Employer.self) - XCTAssert(employer?.providedEmployee.instance.employer !== employer) - } -} diff --git a/Tests/SwinjectTests/SynchronizedTests.swift b/Tests/SwinjectTests/SynchronizedTests.swift deleted file mode 100644 index 8bc58173..00000000 --- a/Tests/SwinjectTests/SynchronizedTests.swift +++ /dev/null @@ -1,282 +0,0 @@ -// -// Copyright © 2021 Swinject Contributors. All rights reserved. -// - -import Dispatch -import XCTest -@testable import Swinject - -class SynchronizedResolverTests: XCTestCase { - - // MARK: Multiple threads - - func testSynchronizedResolverCanResolveCircularDependencies() { - let container = SwinjectContainer { container in - container.register(ParentProtocol.self) { _ in Parent() } - .initCompleted { r, s in - let parent = s as! Parent - parent.child = r.resolve(ChildProtocol.self) - } - .inObjectScope(.graph) - container.register(ChildProtocol.self) { _ in Child() } - .initCompleted { r, s in - let child = s as! Child - child.parent = r.resolve(ParentProtocol.self)! - } - .inObjectScope(.graph) - } - - onMultipleThreads { - let parent = container.resolve(ParentProtocol.self) as! Parent - let child = parent.child as! Child - XCTAssert(child.parent === parent) - } - } - - func testSynchronizedResolverCanAccessParentAndChildContainersWithoutDeadlock() { - let runInObjectScope = { (scope: ObjectScope) in - let parentContainer = SwinjectContainer { container in - container.register(Animal.self) { _ in Cat() } - .inObjectScope(scope) - } - let childResolver = SwinjectContainer(parent: parentContainer) - // swiftlint:disable opening_brace - onMultipleThreads(actions: [ - { _ = parentContainer.resolve(Animal.self) as! Cat }, - { _ = childResolver.resolve(Animal.self) as! Cat }, - ]) - // swiftlint:enable opening_brace - } - - runInObjectScope(.transient) - runInObjectScope(.graph) - runInObjectScope(.container) - } - - func testSynchronizedResolverUsesDistinctGraphIdentifier() { - var graphs = Set() - let container = SwinjectContainer { - $0.register(Dog.self) { - graphs.insert(($0 as! SwinjectContainer).currentObjectGraph!) - return Dog() - } - } - - onMultipleThreads { _ = container.resolve(Dog.self) } - - XCTAssert(graphs.count == totalThreads) - } - - func testSynchronizedResolverSynchronousReadsWrites() { - let iterationCount = 3_000 - let container = SwinjectContainer() - let registerExpectation = expectation(description: "register") - let resolveExpectations = (0..() - let container = SwinjectContainer() - container.register(Animal.self) { - graphs.insert(($0 as! SwinjectContainer).currentObjectGraph!) - return Dog() - } - - onMultipleThreads { - let lazy = container.resolve(Provider.self) - _ = lazy?.instance - } - - XCTAssertEqual(graphs.count, totalThreads) - } - - func testSynchronizedResolverSynchronizesLazyTypes() { - // Lazy types might share graph identifiers and persistent entities. - let container = SwinjectContainer() - container.register(Dog.self) { _ in - return Dog() - } - - let queue = DispatchQueue( - label: "SwinjectTests.SynchronizedContainerSpec.Queue", attributes: .concurrent - ) - waitUntil(timeout: .seconds(2)) { done in - queue.async { - let lazy = container.resolve(Lazy.self) - _ = lazy?.instance - done() - } - } - } - - func testSynchronizedResolverSafelyDereferencesLazyTypes() { - var graphs = Set() - let container = SwinjectContainer() - container.register(Animal.self) { - graphs.insert(($0 as! SwinjectContainer).currentObjectGraph!) - return Dog() - } - .inObjectScope(.container) - - // fast but roughly sufficient to trigger ARC-related crash - for _ in 0..<200 { - onMultipleThreads { - // Lazy will be strongly referenced and then DE-referenced - // which triggers a strong retain cycle on the GraphIdentifier - // which may be simultaneously deallocated on a separate thread - // - // But, since the build with this test uses struct type for - // the GraphIdentifier, this test will succeed. 🎉 - let lazy = container.resolve(Lazy.self) - _ = lazy?.instance - } - } - } - - func testGraphIdentifierRestoredAfterLazyResolve() { - let container = SwinjectContainer() - container.register(LazilyResolvedProtocol.self) { _ in - LazilyResolved() - } - container.register(LazySingletonProtocol.self) { - let lazy = $0.resolve(Lazy.self)! - return LazyChild(lazy: lazy) - } - .inObjectScope(.container) - container.register(LazyChildProtocol.self) { - let lazy = $0.resolve(Lazy.self)! - return LazyChild(lazy: lazy) - } - container.register(LazyParentProtocol.self) { - let child1 = $0.resolve(LazyChildProtocol.self)! - let singleton = $0.resolve(LazySingletonProtocol.self)! - // Previously, accessing instance here would permanently - // hijack the graph identifier to the 'recalled' state. - _ = singleton.lazy.instance - let child2 = $0.resolve(LazyChildProtocol.self)! - return LazyParent(child1: child1, child2: child2) - } - - // Resolve, but don't access lazy value yet. - _ = container.resolve(LazySingletonProtocol.self)! - - // First lazy value access in LazyParent resolve, this - // could've happened in its init or wherever. - let parent = container.resolve(LazyParentProtocol.self)! - - XCTAssertIdentical(parent.child1, parent.child2) - XCTAssertIdentical(parent.child1.lazy.instance, parent.child2.lazy.instance) - } -} - -private final class Counter { - enum Status { - case underMax, reachedMax - } - - private var max: Int - private let lock = DispatchQueue(label: "SwinjectTests.SynchronizedContainerSpec.Counter.Lock", attributes: []) - var count = 0 - - init(max: Int) { - self.max = max - } - - @discardableResult - func increment() -> Status { - var status = Status.underMax - lock.sync { - self.count += 1 - if self.count >= self.max { - status = .reachedMax - } - } - return status - } -} - -private let totalThreads = 500 // 500 threads are enough to get fail unless the container is thread safe. - -private func onMultipleThreads(action: @escaping () -> Void) { - onMultipleThreads(actions: [action]) -} - -private func onMultipleThreads(actions: [() -> Void]) { - waitUntil(timeout: .seconds(2)) { done in - let queue = DispatchQueue( - label: "SwinjectTests.SynchronizedContainerTests.Queue", - attributes: .concurrent - ) - let counter = Counter(max: actions.count * totalThreads) - for _ in 0 ..< totalThreads { - actions.forEach { action in - queue.async { - action() - if counter.increment() == .reachedMax { - done() - } - } - } - } - } -} - -private func waitUntil( - timeout: DispatchTimeInterval, - action: @escaping (@escaping () -> Void) -> Void) { - - let group = DispatchGroup() - group.enter() - - DispatchQueue.global().async { - action { - group.leave() - } - } - - _ = group.wait(timeout: .now() + timeout) -}