Files
Dip/Sources/Definition.swift
2020-05-29 14:42:25 +01:00

324 lines
12 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.
//
///A key used to store definitons in a container.
public struct DefinitionKey: Hashable, CustomStringConvertible {
public let type: Any.Type
public let typeOfArguments: Any.Type
public private(set) var tag: DependencyContainer.Tag?
init(type: Any.Type, typeOfArguments: Any.Type, tag: DependencyContainer.Tag? = nil) {
self.type = type
self.typeOfArguments = typeOfArguments
self.tag = tag
}
public func hash(into hasher: inout Hasher) {
hasher.combine(ObjectIdentifier(type))
hasher.combine(ObjectIdentifier(typeOfArguments))
hasher.combine(tag.desc)
}
public var description: String {
return "type: \(String(reflecting: type)), arguments: \(typeOfArguments), tag: \(tag.desc)"
}
func tagged(with tag: DependencyContainer.Tag?) -> DefinitionKey {
var tagged = self
tagged.tag = tag
return tagged
}
/// Check two definition keys on equality by comparing their `type`, `factoryType` and `tag` properties.
public static func ==(lhs: DefinitionKey, rhs: DefinitionKey) -> Bool {
return
lhs.type == rhs.type &&
lhs.typeOfArguments == rhs.typeOfArguments &&
lhs.tag.desc == rhs.tag.desc
}
}
///Dummy protocol to store definitions for different types in collection
public protocol DefinitionType: AnyObject { }
/**
`Definition<T, U>` describes how instances of type `T` should be created when this type is resolved by the `DependencyContainer`.
- `T` is the type of the instance to resolve
- `U` is the type of runtime arguments accepted by factory that will create an instance of T.
For example `Definition<Service, String>` is the type of definition that will create an instance of type `Service` using factory that accepts `String` argument.
*/
public final class Definition<T, U>: DefinitionType {
public typealias F = (U) throws -> T
//MARK: - _Definition
weak var container: DependencyContainer?
let factory: F
let scope: ComponentScope
var weakFactory: ((Any) throws -> Any)!
var resolveProperties: ((DependencyContainer, Any) throws -> ())?
var autoInjectProperties: Bool?
init(scope: ComponentScope, factory: @escaping F) {
self.factory = factory
self.scope = scope
}
/**
Set the block that will be used to resolve dependencies of the instance.
This block will be called before `resolve(tag:)` returns.
- parameter block: The block to resolve property dependencies of the instance.
- returns: modified definition
- note: To resolve circular dependencies at least one of them should use this block
to resolve its dependencies. Otherwise the application will enter an infinite loop and crash.
- note: You can call this method several times on the same definition.
Container will call all provided blocks in the same order.
**Example**
```swift
container.register { ClientImp(service: try container.resolve() as Service) as Client }
container.register { ServiceImp() as Service }
.resolvingProperties { container, service in
service.client = try container.resolve() as Client
}
```
*/
@discardableResult public func resolvingProperties(_ block: @escaping (DependencyContainer, T) throws -> ()) -> Definition {
if let oldBlock = self.resolveProperties {
self.resolveProperties = {
try oldBlock($0, $1 as! T)
try block($0, $1 as! T)
}
}
else {
self.resolveProperties = { try block($0, $1 as! T) }
}
return self
}
@discardableResult public func resolvingProperty<Root, V>(_ keyPath: ReferenceWritableKeyPath<Root, V>, as type: Any.Type = V.self, tag: DependencyTagConvertible? = nil) -> Definition {
return resolvingProperties { (container, instance) in
precondition(instance is Root, "Type of resolved instance \(Swift.type(of: instance)) does not match expected type \(Root.self)")
let resolved = try container.resolve(type, tag: tag)
precondition(resolved is V, "Type of resolved property \(Swift.type(of: resolved)) does not match expected type \(type)")
(instance as! Root)[keyPath: keyPath] = resolved as! V
}
}
@discardableResult public func resolvingProperty<Root, V>(_ keyPath: ReferenceWritableKeyPath<Root, V>, factory: @escaping (DependencyContainer) throws -> V = { try $0.resolve() }) -> Definition {
return resolvingProperties { (container, instance) in
precondition(instance is Root, "Type of resolved instance \(Swift.type(of: instance)) does not match expected type \(Root.self)")
(instance as! Root)[keyPath: keyPath] = try factory(container)
}
}
/**
Whether container should perform properties auto-injection when resolving using this definition.
If called will override container configuration. Can be called together with `resolvingProperties`
to resolve properties that are not automatically injected.
- parameter shouldAutoInject: Whether container should perform properties auto-injection when resolving using this definition. Default is `true`.
*/
@discardableResult public func autoInjectingProperties(_ shouldAutoInject: Bool = true) -> Definition {
autoInjectProperties = shouldAutoInject
return self
}
/// Calls `resolveDependencies` block if it was set.
func resolveProperties(of instance: Any, container: DependencyContainer) throws {
guard let resolvedInstance = instance as? T else { return }
if let forwardsTo = forwardsTo {
try forwardsTo.resolveProperties(of: resolvedInstance, container: container)
}
if let resolveProperties = self.resolveProperties {
try resolveProperties(container, resolvedInstance)
}
}
//MARK: - AutoWiringDefinition
var autoWiringFactory: ((DependencyContainer, DependencyContainer.Tag?) throws -> Any)?
var numberOfArguments: Int = 0
//MARK: - TypeForwardingDefinition
/// Types that can be resolved using this definition.
private(set) var implementingTypes: [Any.Type] = [(T?).self]
/// Return `true` if type can be resolved using this definition
func doesImplements(type aType: Any.Type) -> Bool {
return implementingTypes.contains(where: { $0 == aType })
}
//MARK: - _TypeForwardingDefinition
/// Adds type as being able to be resolved using this definition
func _implements(type aType: Any.Type) {
_implements(types: [aType])
}
/// Adds types as being able to be resolved using this definition
func _implements(types aTypes: [Any.Type]) {
implementingTypes.append(contentsOf: aTypes.filter({ !doesImplements(type: $0) }))
}
/// Definition to which resolution will be forwarded to
weak var forwardsTo: _TypeForwardingDefinition? {
didSet {
//both definitions (self and forwardsTo) can resolve
//each other types and each other implementing types
//this relationship can be used to reuse previously resolved instances
if let forwardsTo = forwardsTo {
_implements(type: forwardsTo.type)
_implements(types: forwardsTo.implementingTypes)
//definitions for types that can be resolved by `forwardsTo` definition
//can also be used to resolve self type and it's implementing types
//this way container properly reuses previously resolved instances
//when there are several forwarded definitions
//see testThatItReusesInstanceResolvedByTypeForwarding)
for definition in forwardsTo.forwardsFrom {
definition._implements(type: type)
definition._implements(types: implementingTypes)
}
//forwardsTo can be used to resolve self type and it's implementing types
forwardsTo._implements(type: type)
forwardsTo._implements(types: implementingTypes)
forwardsTo.forwardsFrom.append(self)
}
}
}
/// Definitions that will forward resolution to this definition
var forwardsFrom: [_TypeForwardingDefinition] = []
}
//MARK: - _Definition
protocol _Definition: AutoWiringDefinition, TypeForwardingDefinition {
var type: Any.Type { get }
var scope: ComponentScope { get }
var weakFactory: ((Any) throws -> Any)! { get }
func resolveProperties(of instance: Any, container: DependencyContainer) throws
var container: DependencyContainer? { get set }
var autoInjectProperties: Bool? { get }
}
//MARK: - Type Forwarding
protocol _TypeForwardingDefinition: _Definition {
var forwardsTo: _TypeForwardingDefinition? { get }
var forwardsFrom: [_TypeForwardingDefinition] { get set }
func _implements(type aType: Any.Type)
func _implements(types aTypes: [Any.Type])
}
extension Definition: _TypeForwardingDefinition {
var type: Any.Type {
return T.self
}
}
extension Definition: CustomStringConvertible {
public var description: String {
return "type: \(T.self), factory: \(F.self), scope: \(scope)"
}
}
//MARK: - Definition Builder
/// Internal class used to build definition
class DefinitionBuilder<T, U> {
typealias F = (U) throws -> T
var scope: ComponentScope!
var factory: F!
var numberOfArguments: Int = 0
var autoWiringFactory: ((DependencyContainer, DependencyContainer.Tag?) throws -> T)?
var forwardsTo: _Definition?
init(configure: (DefinitionBuilder) -> ()) {
configure(self)
}
func build() -> Definition<T, U> {
let factory = self.factory!
let definition = Definition<T, U>(scope: scope, factory: factory)
definition.numberOfArguments = numberOfArguments
definition.autoWiringFactory = autoWiringFactory
definition.weakFactory = { try factory($0 as! U) }
definition.forwardsTo = forwardsTo as? _TypeForwardingDefinition
return definition
}
}
//MARK: - KeyDefinitionPair
typealias KeyDefinitionPair = (key: DefinitionKey, definition: _Definition)
/// Definitions are matched if they are registered for the same tag and their factories accept the same number of runtime arguments.
private func ~=(lhs: KeyDefinitionPair, rhs: KeyDefinitionPair) -> Bool {
guard lhs.key.type == rhs.key.type else { return false }
guard lhs.key.tag == rhs.key.tag else { return false }
guard lhs.definition.numberOfArguments == rhs.definition.numberOfArguments else { return false }
return true
}
/// Returns key-definition pairs with definitions able to resolve that type (directly or via type forwarding)
/// and which tag matches provided key's tag or is nil if strictByTag is false.
/// In the end filters definitions by type of runtime arguments.
func filter(definitions _definitions: [KeyDefinitionPair], byKey key: DefinitionKey, strictByTag: Bool = false, byTypeOfArguments: Bool = false) -> [KeyDefinitionPair] {
let definitions = _definitions
.filter({ $0.key.type == key.type || $0.definition.doesImplements(type: key.type) })
.filter({ $0.key.tag == key.tag || (!strictByTag && $0.key.tag == nil) })
if byTypeOfArguments {
return definitions.filter({ $0.key.typeOfArguments == key.typeOfArguments })
}
else {
return definitions
}
}
/// Orders key-definition pairs putting first definitions registered for provided tag.
func order(definitions _definitions: [KeyDefinitionPair], byTag tag: DependencyContainer.Tag?) -> [KeyDefinitionPair] {
return
_definitions.filter({ $0.key.tag == tag }) +
_definitions.filter({ $0.key.tag != tag })
}