From 977fc0179ac6280b425ceaa9fce8c3b7590fe18a Mon Sep 17 00:00:00 2001 From: Guillermo Mazzola Date: Fri, 25 Jul 2025 13:37:25 +0200 Subject: [PATCH 1/2] Removed `Component` duplicated code --- Sources/NeedleFoundation/Component.swift | 124 ++++------------------- 1 file changed, 19 insertions(+), 105 deletions(-) diff --git a/Sources/NeedleFoundation/Component.swift b/Sources/NeedleFoundation/Component.swift index 1dc1128..73afbb6 100644 --- a/Sources/NeedleFoundation/Component.swift +++ b/Sources/NeedleFoundation/Component.swift @@ -72,6 +72,8 @@ public class DependencyProvider { } +#endif + /// The base implementation of a dependency injection component. A subclass /// defines a unique scope within the dependency injection tree, that /// contains a set of properties it provides to units of its scope as well @@ -96,23 +98,17 @@ open class Component: Scope { /// /// - note: Accessing this property is not thread-safe. It should only be /// accessed on the same thread as the one that instantiated this component. +#if NEEDLE_DYNAMIC public private(set) var dependency: DependencyProvider! - - /// Initializer. - /// - /// - parameter parent: The parent component of this component. - public init(parent: Scope) { - self.parent = parent - if let canRegister = self as? Registration { - canRegister.registerItems() - } - dependency = DependencyProvider(component: self, nonCore: false) - } +#else + public private(set) var dependency: DependencyType! +#endif /// Initializer. /// /// - parameter parent: The parent component of this component. - public init(parent: Scope, nonCore: Bool) { +#if NEEDLE_DYNAMIC + public init(parent: Scope, nonCore: Bool = false) { self.parent = parent if let canRegister = self as? Registration { @@ -120,6 +116,12 @@ open class Component: Scope { } dependency = DependencyProvider(component: self, nonCore: nonCore) } +#else + public init(parent: Scope) { + self.parent = parent + dependency = createDependencyProvider() + } +#endif /// Share the enclosed object as a singleton at this scope. This allows /// this scope as well as all child scopes to share a single instance of @@ -153,6 +155,8 @@ open class Component: Scope { return instance } +#if NEEDLE_DYNAMIC + public func find(property: String, skipThisLevel: Bool) -> T { guard let itemCloure = localTable[property] else { return parent.find(property: property, skipThisLevel: false) @@ -170,102 +174,14 @@ open class Component: Scope { public var localTable = [String:()->Any]() public var keyPathToName = [PartialKeyPath:String]() - // MARK: - Private - - private let sharedInstanceLock = NSRecursiveLock() - private var sharedInstances = [String: Any]() - private lazy var name: String = { - let fullyQualifiedSelfName = String(describing: self) - let parts = fullyQualifiedSelfName.components(separatedBy: ".") - return parts.last ?? fullyQualifiedSelfName - }() - - // TODO: Replace this with an `open` method, once Swift supports extension - // overriding methods. - private func createDependencyProvider() -> DependencyType { - let provider = __DependencyProviderRegistry.instance.dependencyProvider(for: self) - if let dependency = provider as? DependencyType { - return dependency - } else { - // This case should never occur with properly generated Needle code. - // Needle's official generator should guarantee the correctness. - fatalError("Dependency provider factory for \(self) returned incorrect type. Should be of type \(String(describing: DependencyType.self)). Actual type is \(String(describing: dependency))") - } - } -} - #else -/// The base implementation of a dependency injection component. A subclass -/// defines a unique scope within the dependency injection tree, that -/// contains a set of properties it provides to units of its scope as well -/// as child scopes. A component instantiates child components that define -/// child scopes. -@dynamicMemberLookup -open class Component: Scope { - - /// The parent of this component. - public let parent: Scope - - /// The path to reach this scope on the dependency graph. - // Use `lazy var` to avoid computing the path repeatedly. Internally, - // this is always accessed with the `__DependencyProviderRegistry`'s lock - // acquired. - public lazy var path: [String] = { - let name = self.name - return parent.path + ["\(name)"] - }() - - /// The dependency of this component. - /// - /// - note: Accessing this property is not thread-safe. It should only be - /// accessed on the same thread as the one that instantiated this component. - public private(set) var dependency: DependencyType! - - /// Initializer. - /// - /// - parameter parent: The parent component of this component. - public init(parent: Scope) { - self.parent = parent - dependency = createDependencyProvider() - } - - /// Share the enclosed object as a singleton at this scope. This allows - /// this scope as well as all child scopes to share a single instance of - /// the object, for as long as this component lives. - /// - /// - note: Shared dependency's constructor should avoid switching threads - /// as it may cause a deadlock. - /// - /// - parameter factory: The closure to construct the dependency object. - /// - returns: The dependency object instance. - public final func shared(__function: String = #function, _ factory: () -> T) -> T { - // Use function name as the key, since this is unique per component - // class. At the same time, this is also 150 times faster than - // interpolating the type to convert to string, `"\(T.self)"`. - sharedInstanceLock.lock() - defer { - sharedInstanceLock.unlock() - } - - // Additional nil coalescing is needed to mitigate a Swift bug appearing - // in Xcode 10. see https://bugs.swift.org/browse/SR-8704. Without this - // measure, calling `shared` from a function that returns an optional type - // will always pass the check below and return nil if the instance is not - // initialized. - if let instance = (sharedInstances[__function] as? T?) ?? nil { - return instance - } - let instance = factory() - sharedInstances[__function] = instance - - return instance - } - public subscript(dynamicMember keyPath: KeyPath) -> T { return dependency[keyPath: keyPath] } - + +#endif + // MARK: - Private private let sharedInstanceLock = NSRecursiveLock() @@ -289,5 +205,3 @@ open class Component: Scope { } } } - -#endif From 67fa7cba991b594a4bfa9788302408287ff09bc3 Mon Sep 17 00:00:00 2001 From: Guillermo Mazzola Date: Fri, 25 Jul 2025 14:30:48 +0200 Subject: [PATCH 2/2] Fixed deadlock issue by adding a lock per injection point --- Sources/NeedleFoundation/Component.swift | 39 ++++++++++++++++++------ 1 file changed, 29 insertions(+), 10 deletions(-) diff --git a/Sources/NeedleFoundation/Component.swift b/Sources/NeedleFoundation/Component.swift index 73afbb6..0c1b80b 100644 --- a/Sources/NeedleFoundation/Component.swift +++ b/Sources/NeedleFoundation/Component.swift @@ -133,12 +133,11 @@ open class Component: Scope { /// - parameter factory: The closure to construct the dependency object. /// - returns: The dependency object instance. public final func shared(__function: String = #function, _ factory: () -> T) -> T { - // Use function name as the key, since this is unique per component - // class. At the same time, this is also 150 times faster than - // interpolating the type to convert to string, `"\(T.self)"`. - sharedInstanceLock.lock() + let provider = resolve(__function) + + provider.lock.lock() defer { - sharedInstanceLock.unlock() + provider.lock.unlock() } // Additional nil coalescing is needed to mitigate a Swift bug appearing @@ -146,14 +145,27 @@ open class Component: Scope { // measure, calling `shared` from a function that returns an optional type // will always pass the check below and return nil if the instance is not // initialized. - if let instance = (sharedInstances[__function] as? T?) ?? nil { + if let instance = (provider.instance as? T?) ?? nil { return instance } let instance = factory() - sharedInstances[__function] = instance - + provider.instance = instance return instance } + + private func resolve(_ name: String) -> SingletonProvider { + sharedInstanceLock.lock() + defer { + sharedInstanceLock.unlock() + } + + if let provider = sharedInstances[name] { + return provider + } + let provider = SingletonProvider() + sharedInstances[name] = provider + return provider + } #if NEEDLE_DYNAMIC @@ -183,9 +195,15 @@ open class Component: Scope { #endif // MARK: - Private - + + private class SingletonProvider { + let lock = NSRecursiveLock() + var instance: Any? + } + private let sharedInstanceLock = NSRecursiveLock() - private var sharedInstances = [String: Any]() + private var sharedInstances = [String: SingletonProvider]() + private lazy var name: String = { let fullyQualifiedSelfName = String(describing: self) let parts = fullyQualifiedSelfName.components(separatedBy: ".") @@ -204,4 +222,5 @@ open class Component: Scope { fatalError("Dependency provider factory for \(self) returned incorrect type. Should be of type \(String(describing: DependencyType.self)). Actual type is \(String(describing: dependency))") } } + }