Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
163 changes: 48 additions & 115 deletions Sources/NeedleFoundation/Component.swift
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,8 @@ public class DependencyProvider<DependencyType> {

}

#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
Expand All @@ -96,30 +98,30 @@ open class Component<DependencyType>: 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<DependencyType>!

/// 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 {
canRegister.registerItems()
}
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
Expand All @@ -131,27 +133,41 @@ open class Component<DependencyType>: Scope {
/// - parameter factory: The closure to construct the dependency object.
/// - returns: The dependency object instance.
public final func shared<T>(__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
// 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 {
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

public func find<T>(property: String, skipThisLevel: Bool) -> T {
guard let itemCloure = localTable[property] else {
Expand All @@ -170,106 +186,24 @@ open class Component<DependencyType>: Scope {
public var localTable = [String:()->Any]()
public var keyPathToName = [PartialKeyPath<DependencyType>: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<DependencyType>: 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<T>(__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<T>(dynamicMember keyPath: KeyPath<DependencyType, T>) -> T {
return dependency[keyPath: keyPath]
}


#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: ".")
Expand All @@ -288,6 +222,5 @@ open class Component<DependencyType>: Scope {
fatalError("Dependency provider factory for \(self) returned incorrect type. Should be of type \(String(describing: DependencyType.self)). Actual type is \(String(describing: dependency))")
}
}

}

#endif