529 lines
19 KiB
Swift
529 lines
19 KiB
Swift
//
|
|
// Dip
|
|
//
|
|
// Copyright (c) 2015 Olivier Halligon <olivier@halligon.net>
|
|
//
|
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
// of this software and associated documentation files (the "Software"), to deal
|
|
// in the Software without restriction, including without limitation the rights
|
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
// copies of the Software, and to permit persons to whom the Software is
|
|
// furnished to do so, subject to the following conditions:
|
|
//
|
|
// The above copyright notice and this permission notice shall be included in
|
|
// all copies or substantial portions of the Software.
|
|
//
|
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
// THE SOFTWARE.
|
|
//
|
|
|
|
/**
|
|
`DependencyContainer` allows you to do _Dependency Injection_
|
|
by associating abstractions to concrete implementations.
|
|
*/
|
|
public final class DependencyContainer {
|
|
|
|
/**
|
|
Use a tag in case you need to register multiple factories fo the same type,
|
|
to differentiate them. Tags can be either String or Int, to your convenience.
|
|
|
|
- seealso: `DependencyTagConvertible`
|
|
*/
|
|
public enum Tag {
|
|
case String(StringLiteralType)
|
|
case Int(IntegerLiteralType)
|
|
}
|
|
|
|
var autoInjectProperties: Bool
|
|
var threadSafe: Bool
|
|
internal(set) public var context: Context!
|
|
var definitions = [DefinitionKey: _Definition]()
|
|
var resolvedInstances = ResolvedInstances()
|
|
private let lock = RecursiveLock()
|
|
|
|
var bootstrapped = false
|
|
var bootstrapQueue: [() throws -> ()] = []
|
|
|
|
private var _weakCollaborators: [WeakBox<DependencyContainer>] = []
|
|
var _collaborators: [DependencyContainer] {
|
|
get {
|
|
return _weakCollaborators.compactMap({ $0.value })
|
|
}
|
|
set {
|
|
_weakCollaborators = newValue.filter({ $0 !== self }).map(WeakBox.init)
|
|
}
|
|
}
|
|
|
|
/**
|
|
Designated initializer for a DependencyContainer
|
|
|
|
- Parameters:
|
|
- autoInjectProperties: Whether container should perform properties auto-injection. Default is `true`.
|
|
- threadSafe: Whether container should be thread-safe. Default is `true`. You may want to disable it for better performance.
|
|
- configBlock: A configuration block in which you typically put all you `register` calls.
|
|
|
|
- note: The `configBlock` is simply called at the end of the `init` to let you configure everything.
|
|
It is only present for convenience to have a cleaner syntax when declaring and initializing
|
|
your `DependencyContainer` instances.
|
|
|
|
- warning: If you use `configBlock` you need to make sure you don't create retain cycles. For example
|
|
there will be a retain cycle between container and its definition if you reference container
|
|
inside definition's factory. You can avoid that by using unowned reference to container:
|
|
|
|
```swift
|
|
let container = DependencyContainer() { container in
|
|
unowned let container = container
|
|
//register definitions
|
|
}
|
|
```
|
|
|
|
- returns: A new DependencyContainer.
|
|
*/
|
|
public init(autoInjectProperties: Bool = true, threadSafe: Bool = true, configBlock: (DependencyContainer)->() = { _ in }) {
|
|
self.autoInjectProperties = autoInjectProperties
|
|
self.threadSafe = threadSafe
|
|
configBlock(self)
|
|
}
|
|
|
|
/**
|
|
Call this method to complete container setup. After container is bootstrapped
|
|
you can not add or remove definitions. Trying to do so will cause runtime exception.
|
|
You can completely reset container, after reset you can bootstrap it again.
|
|
During bootstrap container will instantiate components registered with `EagerSingleton` scope.
|
|
|
|
- throws: `DipError` if failed to instantiate any component
|
|
*/
|
|
public func bootstrap() throws {
|
|
try threadSafe {
|
|
bootstrapped = true
|
|
try bootstrapQueue.forEach({ try $0() })
|
|
bootstrapQueue.removeAll()
|
|
}
|
|
}
|
|
|
|
func threadSafe<T>(_ closure: () throws -> T) rethrows -> T {
|
|
guard threadSafe else {
|
|
return try closure()
|
|
}
|
|
|
|
lock.lock()
|
|
defer { lock.unlock() }
|
|
return try closure()
|
|
}
|
|
|
|
}
|
|
|
|
extension DependencyContainer {
|
|
|
|
/**
|
|
Context provides contextual information about resolution process.
|
|
|
|
You can use the context for debugging or to pass through tag when you explicitly resolve dependencies.
|
|
When auto-wiring or auto-injecting tag will be implicitly passed through by the container.
|
|
For auto-injected properties you can disable that by providing tag (some value or `nil`) when defining property.
|
|
|
|
**Example**:
|
|
|
|
```swift
|
|
class SomeServiceImp: SomeService {
|
|
//container will pass through the tag ("tag") used to resolve containing instance to resolve this property
|
|
let injected = Injected<SomeDependency>()
|
|
|
|
//container will use "someTag" tag to resolve this property
|
|
let injectedTagged = Injected<SomeDependency>(tag: "someTag")
|
|
|
|
//container will use `nil` tag to resolve this property
|
|
let injectedNilTag = Injected<SomeDependency>(tag: nil)
|
|
}
|
|
|
|
container.register {
|
|
//container will pass through the tag ("tag") used to resolve SomeService to resolve $0
|
|
SomeServiceImp(dependency: $0) as SomeService
|
|
}.resolvingProperties { container, service in
|
|
//container will use `nil` tag to resolve this dependency
|
|
self.dependency = try container.resolve() as SomeDependency
|
|
|
|
//container will use current context tag ("tag") to resolve this dependency
|
|
self.taggedDependency = try container.resolve(tag: container.context.tag) as SomeDependency
|
|
}
|
|
|
|
//container will use "tag" to resolve this instance
|
|
let service = try! container.resolve(tag: "tag") as SomeService
|
|
|
|
```
|
|
*/
|
|
public struct Context: CustomStringConvertible, CustomDebugStringConvertible {
|
|
|
|
internal(set) public var key: DefinitionKey
|
|
|
|
/// Currently resolving type.
|
|
public var resolvingType: Any.Type {
|
|
return key.type
|
|
}
|
|
|
|
/// The tag used to resolve currently resolving type.
|
|
public var tag: Tag? {
|
|
return key.tag
|
|
}
|
|
|
|
/// The type that caused currently resolving type to be resolved.
|
|
/// `nil` for root object in a dependencies graph.
|
|
private(set) public var injectedInType: Any.Type?
|
|
|
|
/// The label of the property where resolved instance will be auto-injected.
|
|
private(set) public var injectedInProperty: String?
|
|
|
|
let inCollaboration: Bool
|
|
|
|
var logErrors: Bool = true
|
|
|
|
init(key: DefinitionKey, injectedInType: Any.Type?, injectedInProperty: String?, inCollaboration: Bool) {
|
|
self.key = key
|
|
self.injectedInType = injectedInType
|
|
self.injectedInProperty = injectedInProperty
|
|
self.inCollaboration = inCollaboration
|
|
}
|
|
|
|
public var debugDescription: String {
|
|
return "Context(key: \(key), injectedInType: \(injectedInType.desc), injectedInProperty: \(injectedInProperty.desc) logErrors: \(logErrors))"
|
|
}
|
|
|
|
public var description: String {
|
|
let resolvingDescription = "Resolving type \(key.type) with arguments \(key.typeOfArguments) \(key.tag != nil ? "tagged with \(key.tag!)" : "")"
|
|
if injectedInProperty != nil {
|
|
return "\(resolvingDescription) while auto-injecting property \(injectedInProperty.desc) of \(injectedInType.desc)"
|
|
}
|
|
else if injectedInType != nil {
|
|
return "\(resolvingDescription) while injecting in type \(injectedInType.desc)"
|
|
}
|
|
else {
|
|
return resolvingDescription
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
/// Pushes new context created with provided values and calls block. When block returns previous context is restored.
|
|
/// When popped to initial (root) context will release all references to resolved instances and call `Resolvable` callbacks.
|
|
func inContext<T>(key aKey: DefinitionKey, injectedInType: Any.Type?, injectedInProperty: String? = nil, inCollaboration: Bool = false, container: DependencyContainer? = nil, logErrors: Bool! = nil, block: () throws -> T) rethrows -> T {
|
|
let key = aKey
|
|
return try threadSafe {
|
|
let currentContext = self.context
|
|
|
|
defer {
|
|
context = currentContext
|
|
|
|
//clean instances pool if it is owned not by other container
|
|
if context == nil {
|
|
resolvedInstances.resolvedInstances.removeAll()
|
|
for (key, instance) in resolvedInstances.sharedWeakSingletons {
|
|
if resolvedInstances.sharedWeakSingletons[key] is WeakBoxType { continue }
|
|
resolvedInstances.sharedWeakSingletons[key] = WeakBox(instance)
|
|
}
|
|
for (key, instance) in resolvedInstances.weakSingletons {
|
|
if resolvedInstances.weakSingletons[key] is WeakBoxType { continue }
|
|
resolvedInstances.weakSingletons[key] = WeakBox(instance)
|
|
}
|
|
|
|
for resolvedInstance in resolvedInstances.resolvableInstances.reversed() {
|
|
resolvedInstance.didResolveDependencies()
|
|
}
|
|
resolvedInstances.resolvableInstances.removeAll()
|
|
}
|
|
}
|
|
|
|
context = Context(
|
|
key: key,
|
|
injectedInType: injectedInType,
|
|
injectedInProperty: injectedInProperty,
|
|
inCollaboration: inCollaboration
|
|
)
|
|
context.logErrors = logErrors ?? currentContext?.logErrors ?? true
|
|
|
|
do {
|
|
return try block()
|
|
}
|
|
catch {
|
|
if context.logErrors { log(level: .Errors, error) }
|
|
throw error
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
//MARK: - Collaborating containers
|
|
|
|
extension DependencyContainer {
|
|
|
|
/**
|
|
Adds collaborating containers as weak references. Circular references are allowed.
|
|
References to the container itself are ignored.
|
|
*/
|
|
public func collaborate(with containers: DependencyContainer...) {
|
|
collaborate(with: containers)
|
|
}
|
|
|
|
/**
|
|
Adds collaborating containers as weak references. Circular references are allowed.
|
|
References to the container itself are ignored.
|
|
*/
|
|
public func collaborate(with containers: [DependencyContainer]) {
|
|
_collaborators += containers
|
|
for container in containers {
|
|
container._collaborators += [self]
|
|
container.resolvedInstances.sharedSingletonsBox = self.resolvedInstances.sharedSingletonsBox
|
|
container.resolvedInstances.sharedWeakSingletonsBox = self.resolvedInstances.sharedWeakSingletonsBox
|
|
updateCollaborationReferences(between: container, and: self)
|
|
}
|
|
}
|
|
|
|
private func updateCollaborationReferences(between container: DependencyContainer, and collaborator: DependencyContainer) {
|
|
for container in container._collaborators {
|
|
guard container.resolvedInstances.sharedSingletonsBox !== collaborator.resolvedInstances.sharedSingletonsBox else { continue }
|
|
container.resolvedInstances.sharedSingletonsBox = collaborator.resolvedInstances.sharedSingletonsBox
|
|
container.resolvedInstances.sharedWeakSingletonsBox = collaborator.resolvedInstances.sharedWeakSingletonsBox
|
|
updateCollaborationReferences(between: container, and: collaborator)
|
|
}
|
|
}
|
|
|
|
/// Tries to resolve key using collaborating containers
|
|
func collaboratingResolve<T>(key aKey: DefinitionKey, builder: (_Definition) throws -> T) -> T? {
|
|
let key = aKey
|
|
for collaborator in _collaborators {
|
|
//if container is already in a context resolving this type
|
|
//it means that it has been already called to resolve this type,
|
|
//so there is probably a circular reference between containers.
|
|
//To break it skip this container
|
|
if let context = collaborator.context, context.resolvingType == key.type && context.tag == key.tag { continue }
|
|
|
|
do {
|
|
//Pass current container's instances pool to collect instances resolved by collaborator
|
|
let resolvedInstances = collaborator.resolvedInstances
|
|
collaborator.resolvedInstances = self.resolvedInstances
|
|
//Set collaborator context to preserve current container context
|
|
let context = collaborator.context
|
|
collaborator.context = self.context
|
|
defer {
|
|
collaborator.context = context
|
|
collaborator.resolvedInstances = resolvedInstances
|
|
|
|
for (key, resolvedSingleton) in self.resolvedInstances.singletons {
|
|
collaborator.resolvedInstances.singletons[key] = resolvedSingleton
|
|
}
|
|
for (key, resolvedSingleton) in self.resolvedInstances.weakSingletons {
|
|
guard collaborator.definition(matching: key) == nil else { continue }
|
|
collaborator.resolvedInstances.weakSingletons[key] = resolvedSingleton is WeakBoxType ? resolvedSingleton : WeakBox(resolvedSingleton)
|
|
}
|
|
for (key, resolved) in self.resolvedInstances.resolvedInstances {
|
|
guard collaborator.definition(matching: key) == nil else { continue }
|
|
collaborator.resolvedInstances.resolvedInstances[key] = resolved
|
|
}
|
|
}
|
|
|
|
let resolved = try collaborator.inContext(key:key, injectedInType: self.context.injectedInType, injectedInProperty: self.context.injectedInProperty, inCollaboration: true, logErrors: false) {
|
|
try collaborator._resolve(key: key, builder: builder)
|
|
}
|
|
|
|
return resolved
|
|
}
|
|
catch { }
|
|
}
|
|
return nil
|
|
}
|
|
|
|
}
|
|
|
|
// MARK: - Removing definitions
|
|
|
|
extension DependencyContainer {
|
|
|
|
/**
|
|
Removes definition registered in the container.
|
|
|
|
- parameters:
|
|
- tag: The tag used to register definition.
|
|
- definition: The definition to remove
|
|
*/
|
|
public func remove<T, U>(_ definition: Definition<T, U>, tag: DependencyTagConvertible? = nil) {
|
|
_remove(definition: definition, tag: tag)
|
|
}
|
|
|
|
func _remove<T, U>(definition aDefinition: Definition<T, U>, tag: DependencyTagConvertible? = nil) {
|
|
let key = DefinitionKey(type: T.self, typeOfArguments: U.self, tag: tag?.dependencyTag)
|
|
_remove(definitionForKey: key)
|
|
}
|
|
|
|
func _remove(definitionForKey key: DefinitionKey) {
|
|
precondition(!bootstrapped, "You can not modify container's definitions after it was bootstrapped.")
|
|
|
|
threadSafe {
|
|
definitions[key]?.container = nil
|
|
definitions[key] = nil
|
|
resolvedInstances.singletons[key] = nil
|
|
resolvedInstances.weakSingletons[key] = nil
|
|
resolvedInstances.sharedSingletons[key] = nil
|
|
resolvedInstances.sharedWeakSingletons[key] = nil
|
|
}
|
|
}
|
|
|
|
/**
|
|
Removes all definitions registered in the container.
|
|
*/
|
|
public func reset() {
|
|
threadSafe {
|
|
definitions.forEach { $0.1.container = nil }
|
|
definitions.removeAll()
|
|
resolvedInstances.singletons.removeAll()
|
|
resolvedInstances.weakSingletons.removeAll()
|
|
resolvedInstances.sharedSingletons.removeAll()
|
|
resolvedInstances.sharedWeakSingletons.removeAll()
|
|
bootstrapped = false
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
// MARK: - Validation
|
|
|
|
extension DependencyContainer {
|
|
|
|
/**
|
|
Validates container configuration trying to resolve each registered definition one by one.
|
|
If definition fails to be resolved without arguments will search provided arguments array
|
|
for arguments matched by type and try to resolve this definition using these arguments.
|
|
If there are no matching arguments will rethrow original error.
|
|
|
|
- parameter arguments: set of arguments to use to resolve registered definitions.
|
|
Use a tuple for registered factories that accept several runtime arguments.
|
|
*/
|
|
public func validate(_ arguments: Any...) throws {
|
|
try _validate(arguments: arguments)
|
|
}
|
|
|
|
func _validate(arguments _arguments: [Any]) throws {
|
|
let arguments = _arguments
|
|
validateNextDefinition: for (key, _) in definitions {
|
|
do {
|
|
//try to resolve key using provided arguments
|
|
for argumentsSet in arguments {
|
|
guard type(of: argumentsSet) == key.typeOfArguments else { continue }
|
|
do {
|
|
let _ = try inContext(key:key, injectedInType: nil) {
|
|
try self._resolve(key: key, builder: { definition throws -> Any in
|
|
try definition.weakFactory(argumentsSet)
|
|
})
|
|
}
|
|
continue validateNextDefinition
|
|
}
|
|
catch let error as DipError {
|
|
throw error
|
|
}
|
|
//ignore other errors
|
|
catch { log(level: .Errors, error) }
|
|
}
|
|
|
|
//try to resolve key using auto-wiring
|
|
do {
|
|
let _ = try self.resolve(key.type, tag: key.tag)
|
|
}
|
|
catch let error as DipError {
|
|
throw error
|
|
}
|
|
//ignore other errors
|
|
catch { log(level: .Errors, error) }
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
extension DependencyContainer: CustomStringConvertible {
|
|
|
|
public var description: String {
|
|
return "Definitions: \(definitions.count)\n" + definitions.map({ "\($0.0)" }).joined(separator: "\n")
|
|
}
|
|
|
|
}
|
|
|
|
//MARK: - DependencyTagConvertible
|
|
|
|
/// Implement this protocol on your type if you want to use its instances as `DependencyContainer`'s tags.
|
|
/// `DependencyContainer.Tag`, `String`, `Int` and any `RawRepresentable` with `RawType` of `String` or `Int` by default conform to this protocol.
|
|
public protocol DependencyTagConvertible {
|
|
var dependencyTag: DependencyContainer.Tag { get }
|
|
}
|
|
|
|
extension DependencyContainer.Tag: DependencyTagConvertible {
|
|
public var dependencyTag: DependencyContainer.Tag {
|
|
return self
|
|
}
|
|
}
|
|
|
|
extension String: DependencyTagConvertible {
|
|
public var dependencyTag: DependencyContainer.Tag {
|
|
return .String(self)
|
|
}
|
|
}
|
|
|
|
extension Int: DependencyTagConvertible {
|
|
public var dependencyTag: DependencyContainer.Tag {
|
|
return .Int(self)
|
|
}
|
|
}
|
|
|
|
extension DependencyTagConvertible where Self: RawRepresentable, Self.RawValue == Int {
|
|
public var dependencyTag: DependencyContainer.Tag {
|
|
return .Int(rawValue)
|
|
}
|
|
}
|
|
|
|
extension DependencyTagConvertible where Self: RawRepresentable, Self.RawValue == String {
|
|
public var dependencyTag: DependencyContainer.Tag {
|
|
return .String(rawValue)
|
|
}
|
|
}
|
|
|
|
extension DependencyContainer.Tag: ExpressibleByStringLiteral {
|
|
|
|
public init(stringLiteral value: StringLiteralType) {
|
|
self = .String(value)
|
|
}
|
|
|
|
public init(unicodeScalarLiteral value: StringLiteralType) {
|
|
self.init(stringLiteral: value)
|
|
}
|
|
|
|
public init(extendedGraphemeClusterLiteral value: StringLiteralType) {
|
|
self.init(stringLiteral: value)
|
|
}
|
|
|
|
}
|
|
|
|
extension DependencyContainer.Tag: ExpressibleByIntegerLiteral {
|
|
|
|
public init(integerLiteral value: IntegerLiteralType) {
|
|
self = .Int(value)
|
|
}
|
|
|
|
}
|
|
|
|
extension DependencyContainer.Tag: Equatable {
|
|
|
|
public static func ==(lhs: DependencyContainer.Tag, rhs: DependencyContainer.Tag) -> Bool {
|
|
switch (lhs, rhs) {
|
|
case let (.String(lhsString), .String(rhsString)):
|
|
return lhsString == rhsString
|
|
case let (.Int(lhsInt), .Int(rhsInt)):
|
|
return lhsInt == rhsInt
|
|
default:
|
|
return false
|
|
}
|
|
}
|
|
|
|
}
|