Sources/NeedleFoundation/Component.swift (145 lines of code) (raw):
//
// Copyright (c) 2018. Uber Technologies
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
import Foundation
/// The base protocol of a dependency, enabling Needle's parsing process.
public protocol Dependency: AnyObject {}
#if NEEDLE_DYNAMIC
public protocol Registration {
func registerItems()
}
#endif
/// The base protocol of a DI scope. Application code should inherit
/// from the `Component` base class, instead of using this protocol
/// directly.
/// @CreateMock
public protocol Scope: AnyObject {
/// The path to reach this component on the dependency graph.
var path: [String] { get }
/// The parent of this component.
var parent: NeedleFoundation.Scope { get }
#if NEEDLE_DYNAMIC
func find<T>(property: String, skipThisLevel: Bool) -> T
#endif
}
#if NEEDLE_DYNAMIC
@dynamicMemberLookup
public class DependencyProvider<DependencyType> {
/// The parent component of this provider.
weak var component: Component<DependencyType>?
let nonCore: Bool
init(component: Component<DependencyType>, nonCore: Bool) {
self.component = component
self.nonCore = nonCore
}
public func find<T>(property: String) -> T {
return component!.parent.find(property: property, skipThisLevel: nonCore)
}
public subscript<T>(dynamicMember keyPath: KeyPath<DependencyType, T>) -> T {
return lookup(keyPath: keyPath)
}
public func lookup<T>(keyPath: KeyPath<DependencyType, T>) -> T {
guard let propertyName = component!.keyPathToName[keyPath] else {
fatalError("Cound not find \(keyPath) in lookup table")
}
return find(property: propertyName)
}
}
/// 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: 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)
}
/// Initializer.
///
/// - parameter parent: The parent component of this component.
public init(parent: Scope, nonCore: Bool) {
self.parent = parent
if let canRegister = self as? Registration {
canRegister.registerItems()
}
dependency = DependencyProvider(component: self, nonCore: nonCore)
}
/// 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 func find<T>(property: String, skipThisLevel: Bool) -> T {
guard let itemCloure = localTable[property] else {
return parent.find(property: property, skipThisLevel: false)
}
guard let result = itemCloure() as? T else {
fatalError("Incorrect type for \(property) found lookup table")
}
return result
}
public subscript<T>(dynamicMember keyPath: KeyPath<DependencyType, T>) -> T {
return dependency.lookup(keyPath: keyPath)
}
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]
}
// 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))")
}
}
}
#endif