You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
343 lines
13 KiB
343 lines
13 KiB
//
|
|
// Copyright © 2019 Swinject Contributors. All rights reserved.
|
|
//
|
|
|
|
import Foundation
|
|
|
|
/// The `Container` class represents a dependency injection container, which stores registrations of services
|
|
/// and retrieves registered services with dependencies injected.
|
|
///
|
|
/// **Example to register:**
|
|
///
|
|
/// let container = Container()
|
|
/// container.register(A.self) { _ in B() }
|
|
/// container.register(X.self) { r in Y(a: r.resolve(A.self)!) }
|
|
///
|
|
/// **Example to retrieve:**
|
|
///
|
|
/// let x = container.resolve(X.self)!
|
|
///
|
|
/// where `A` and `X` are protocols, `B` is a type conforming `A`, and `Y` is a type conforming `X`
|
|
/// and depending on `A`.
|
|
public final class Container {
|
|
internal var services = [ServiceKey: ServiceEntryProtocol]()
|
|
private let parent: Container? // Used by HierarchyObjectScope
|
|
private var resolutionDepth = 0
|
|
private let debugHelper: DebugHelper
|
|
private let defaultObjectScope: ObjectScope
|
|
internal var currentObjectGraph: GraphIdentifier?
|
|
internal let lock: SpinLock // Used by SynchronizedResolver.
|
|
internal var behaviors = [Behavior]()
|
|
|
|
internal init(
|
|
parent: Container? = nil,
|
|
debugHelper: DebugHelper,
|
|
defaultObjectScope: ObjectScope = .graph
|
|
) {
|
|
self.parent = parent
|
|
self.debugHelper = debugHelper
|
|
lock = parent.map { $0.lock } ?? SpinLock()
|
|
self.defaultObjectScope = defaultObjectScope
|
|
}
|
|
|
|
/// Instantiates a `Container`
|
|
///
|
|
/// - Parameters
|
|
/// - parent: The optional parent `Container`.
|
|
/// - defaultObjectScope: Default object scope (graph if no scope is injected)
|
|
/// - behaviors: List of behaviors to be added to the container
|
|
/// - registeringClosure: The closure registering services to the new container instance.
|
|
///
|
|
/// - Remark: Compile time may be long if you pass a long closure to this initializer.
|
|
/// Use `init()` or `init(parent:)` instead.
|
|
public convenience init(
|
|
parent: Container? = nil,
|
|
defaultObjectScope: ObjectScope = .graph,
|
|
behaviors: [Behavior] = [],
|
|
registeringClosure: (Container) -> Void = { _ in }
|
|
) {
|
|
self.init(parent: parent, debugHelper: LoggingDebugHelper(), defaultObjectScope: defaultObjectScope)
|
|
behaviors.forEach(addBehavior)
|
|
registeringClosure(self)
|
|
}
|
|
|
|
/// Removes all registrations in the container.
|
|
public func removeAll() {
|
|
services.removeAll()
|
|
}
|
|
|
|
/// Discards instances for services registered in the given `ObjectsScopeProtocol`.
|
|
///
|
|
/// **Example usage:**
|
|
/// container.resetObjectScope(ObjectScope.container)
|
|
///
|
|
/// - Parameters:
|
|
/// - objectScope: All instances registered in given `ObjectsScopeProtocol` will be discarded.
|
|
public func resetObjectScope(_ objectScope: ObjectScopeProtocol) {
|
|
services.values
|
|
.filter { $0.objectScope === objectScope }
|
|
.forEach { $0.storage.instance = nil }
|
|
|
|
parent?.resetObjectScope(objectScope)
|
|
}
|
|
|
|
/// Discards instances for services registered in the given `ObjectsScope`. It performs the same operation
|
|
/// as `resetObjectScope(_:ObjectScopeProtocol)`, but provides more convenient usage syntax.
|
|
///
|
|
/// **Example usage:**
|
|
/// container.resetObjectScope(.container)
|
|
///
|
|
/// - Parameters:
|
|
/// - objectScope: All instances registered in given `ObjectsScope` will be discarded.
|
|
public func resetObjectScope(_ objectScope: ObjectScope) {
|
|
resetObjectScope(objectScope as ObjectScopeProtocol)
|
|
}
|
|
|
|
/// Adds a registration for the specified service with the factory closure to specify how the service is
|
|
/// resolved with dependencies.
|
|
///
|
|
/// - Parameters:
|
|
/// - serviceType: The service type to register.
|
|
/// - name: A registration name, which is used to differentiate from other registrations
|
|
/// that have the same service and factory types.
|
|
/// - factory: The closure to specify how the service type is resolved with the dependencies of the type.
|
|
/// It is invoked when the `Container` needs to instantiate the instance.
|
|
/// It takes a `Resolver` to inject dependencies to the instance,
|
|
/// and returns the instance of the component type for the service.
|
|
///
|
|
/// - Returns: A registered `ServiceEntry` to configure more settings with method chaining.
|
|
@discardableResult
|
|
public func register<Service>(
|
|
_ serviceType: Service.Type,
|
|
name: String? = nil,
|
|
factory: @escaping (Resolver) -> Service
|
|
) -> ServiceEntry<Service> {
|
|
return _register(serviceType, factory: factory, name: name)
|
|
}
|
|
|
|
/// This method is designed for the use to extend Swinject functionality.
|
|
/// Do NOT use this method unless you intend to write an extension or plugin to Swinject framework.
|
|
///
|
|
/// - Parameters:
|
|
/// - serviceType: The service type to register.
|
|
/// - factory: The closure to specify how the service type is resolved with the dependencies of the type.
|
|
/// It is invoked when the `Container` needs to instantiate the instance.
|
|
/// It takes a `Resolver` to inject dependencies to the instance,
|
|
/// and returns the instance of the component type for the service.
|
|
/// - name: A registration name.
|
|
/// - option: A service key option for an extension/plugin.
|
|
///
|
|
/// - Returns: A registered `ServiceEntry` to configure more settings with method chaining.
|
|
@discardableResult
|
|
// swiftlint:disable:next identifier_name
|
|
public func _register<Service, Arguments>(
|
|
_ serviceType: Service.Type,
|
|
factory: @escaping (Arguments) -> Any,
|
|
name: String? = nil,
|
|
option: ServiceKeyOption? = nil
|
|
) -> ServiceEntry<Service> {
|
|
let key = ServiceKey(serviceType: Service.self, argumentsType: Arguments.self, name: name, option: option)
|
|
let entry = ServiceEntry(
|
|
serviceType: serviceType,
|
|
argumentsType: Arguments.self,
|
|
factory: factory,
|
|
objectScope: defaultObjectScope
|
|
)
|
|
entry.container = self
|
|
services[key] = entry
|
|
|
|
behaviors.forEach { $0.container(self, didRegisterType: serviceType, toService: entry, withName: name) }
|
|
|
|
return entry
|
|
}
|
|
|
|
/// Returns a synchronized view of the container for thread safety.
|
|
/// The returned container is `Resolver` type. Call this method after you finish all service registrations
|
|
/// to the original container.
|
|
///
|
|
/// - Returns: A synchronized container as `Resolver`.
|
|
public func synchronize() -> Resolver {
|
|
return SynchronizedResolver(container: self)
|
|
}
|
|
|
|
/// Adds behavior to the container. `Behavior.container(_:didRegisterService:withName:)` will be invoked for
|
|
/// each service registered to the `container` **after** the behavior has been added.
|
|
///
|
|
/// - Parameters:
|
|
/// - behavior: Behavior to be added to the container
|
|
public func addBehavior(_ behavior: Behavior) {
|
|
behaviors.append(behavior)
|
|
}
|
|
|
|
internal func restoreObjectGraph(_ identifier: GraphIdentifier) {
|
|
currentObjectGraph = identifier
|
|
}
|
|
}
|
|
|
|
// MARK: - _Resolver
|
|
|
|
extension Container: _Resolver {
|
|
// swiftlint:disable:next identifier_name
|
|
public func _resolve<Service, Arguments>(
|
|
name: String?,
|
|
option: ServiceKeyOption? = nil,
|
|
invoker: @escaping ((Arguments) -> Any) -> Any
|
|
) -> Service? {
|
|
var resolvedInstance: Service?
|
|
let key = ServiceKey(serviceType: Service.self, argumentsType: Arguments.self, name: name, option: option)
|
|
|
|
if let entry = getEntry(for: key) {
|
|
resolvedInstance = resolve(entry: entry, invoker: invoker)
|
|
}
|
|
|
|
if resolvedInstance == nil {
|
|
resolvedInstance = resolveAsWrapper(name: name, option: option, invoker: invoker)
|
|
}
|
|
|
|
if resolvedInstance == nil {
|
|
debugHelper.resolutionFailed(
|
|
serviceType: Service.self,
|
|
key: key,
|
|
availableRegistrations: getRegistrations()
|
|
)
|
|
}
|
|
|
|
return resolvedInstance
|
|
}
|
|
|
|
fileprivate func resolveAsWrapper<Wrapper, Arguments>(
|
|
name: String?,
|
|
option: ServiceKeyOption?,
|
|
invoker: @escaping ((Arguments) -> Any) -> Any
|
|
) -> Wrapper? {
|
|
guard let wrapper = Wrapper.self as? InstanceWrapper.Type else { return nil }
|
|
|
|
let key = ServiceKey(
|
|
serviceType: wrapper.wrappedType, argumentsType: Arguments.self, name: name, option: option
|
|
)
|
|
|
|
if let entry = getEntry(for: key) {
|
|
let factory = { [weak self] in self?.resolve(entry: entry, invoker: invoker) as Any? }
|
|
return wrapper.init(inContainer: self, withInstanceFactory: factory) as? Wrapper
|
|
} else {
|
|
return wrapper.init(inContainer: self, withInstanceFactory: nil) as? Wrapper
|
|
}
|
|
}
|
|
|
|
fileprivate func getRegistrations() -> [ServiceKey: ServiceEntryProtocol] {
|
|
var registrations = parent?.getRegistrations() ?? [:]
|
|
services.forEach { key, value in registrations[key] = value }
|
|
return registrations
|
|
}
|
|
|
|
fileprivate var maxResolutionDepth: Int { return 200 }
|
|
|
|
fileprivate func incrementResolutionDepth() {
|
|
parent?.incrementResolutionDepth()
|
|
if resolutionDepth == 0, currentObjectGraph == nil {
|
|
currentObjectGraph = GraphIdentifier()
|
|
}
|
|
guard resolutionDepth < maxResolutionDepth else {
|
|
fatalError("Infinite recursive call for circular dependency has been detected. " +
|
|
"To avoid the infinite call, 'initCompleted' handler should be used to inject circular dependency.")
|
|
}
|
|
resolutionDepth += 1
|
|
}
|
|
|
|
fileprivate func decrementResolutionDepth() {
|
|
parent?.decrementResolutionDepth()
|
|
assert(resolutionDepth > 0, "The depth cannot be negative.")
|
|
|
|
resolutionDepth -= 1
|
|
if resolutionDepth == 0 { graphResolutionCompleted() }
|
|
}
|
|
|
|
fileprivate func graphResolutionCompleted() {
|
|
services.values.forEach { $0.storage.graphResolutionCompleted() }
|
|
currentObjectGraph = nil
|
|
}
|
|
}
|
|
|
|
// MARK: - Resolver
|
|
|
|
extension Container: Resolver {
|
|
/// Retrieves the instance with the specified service type.
|
|
///
|
|
/// - Parameter serviceType: The service type to resolve.
|
|
///
|
|
/// - Returns: The resolved service type instance, or nil if no registration for the service type
|
|
/// is found in the `Container`.
|
|
public func resolve<Service>(_ serviceType: Service.Type) -> Service? {
|
|
return resolve(serviceType, name: nil)
|
|
}
|
|
|
|
/// Retrieves the instance with the specified service type and registration name.
|
|
///
|
|
/// - Parameters:
|
|
/// - serviceType: The service type to resolve.
|
|
/// - name: The registration name.
|
|
///
|
|
/// - Returns: The resolved service type instance, or nil if no registration for the service type and name
|
|
/// is found in the `Container`.
|
|
public func resolve<Service>(_: Service.Type, name: String?) -> Service? {
|
|
return _resolve(name: name) { (factory: (Resolver) -> Any) in factory(self) }
|
|
}
|
|
|
|
fileprivate func getEntry(for key: ServiceKey) -> ServiceEntryProtocol? {
|
|
if let entry = services[key] {
|
|
return entry
|
|
} else {
|
|
return parent?.getEntry(for: key)
|
|
}
|
|
}
|
|
|
|
fileprivate func resolve<Service, Factory>(
|
|
entry: ServiceEntryProtocol,
|
|
invoker: (Factory) -> Any
|
|
) -> Service? {
|
|
incrementResolutionDepth()
|
|
defer { decrementResolutionDepth() }
|
|
|
|
guard let currentObjectGraph = currentObjectGraph else {
|
|
fatalError("If accessing container from multiple threads, make sure to use a synchronized resolver.")
|
|
}
|
|
|
|
if let persistedInstance = persistedInstance(Service.self, from: entry, in: currentObjectGraph) {
|
|
return persistedInstance
|
|
}
|
|
|
|
let resolvedInstance = invoker(entry.factory as! Factory)
|
|
if let persistedInstance = persistedInstance(Service.self, from: entry, in: currentObjectGraph) {
|
|
// An instance for the key might be added by the factory invocation.
|
|
return persistedInstance
|
|
}
|
|
entry.storage.setInstance(resolvedInstance as Any, inGraph: currentObjectGraph)
|
|
|
|
if let completed = entry.initCompleted as? (Resolver, Any) -> Void,
|
|
let resolvedInstance = resolvedInstance as? Service {
|
|
completed(self, resolvedInstance)
|
|
}
|
|
|
|
return resolvedInstance as? Service
|
|
}
|
|
|
|
private func persistedInstance<Service>(
|
|
_: Service.Type, from entry: ServiceEntryProtocol, in graph: GraphIdentifier
|
|
) -> Service? {
|
|
if let instance = entry.storage.instance(inGraph: graph), let service = instance as? Service {
|
|
return service
|
|
} else {
|
|
return nil
|
|
}
|
|
}
|
|
}
|
|
|
|
// MARK: CustomStringConvertible
|
|
|
|
extension Container: CustomStringConvertible {
|
|
public var description: String {
|
|
return "["
|
|
+ services.map { "\n { \($1.describeWithKey($0)) }" }.sorted().joined(separator: ",")
|
|
+ "\n]"
|
|
}
|
|
}
|