336 lines
13 KiB
Swift
336 lines
13 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.
|
|
//
|
|
|
|
extension DependencyContainer {
|
|
|
|
/**
|
|
Resolve an instance of type `T`.
|
|
|
|
If no matching definition was registered with provided `tag`,
|
|
container will lookup definition associated with `nil` tag.
|
|
|
|
- parameter tag: The arbitrary tag to use to lookup definition.
|
|
|
|
- throws: `DipError.DefinitionNotFound`, `DipError.AutoInjectionFailed`, `DipError.AmbiguousDefinitions`, `DipError.InvalidType`
|
|
|
|
- returns: An instance of type `T`.
|
|
|
|
**Example**:
|
|
```swift
|
|
let service = try! container.resolve() as Service
|
|
let service = try! container.resolve(tag: "service") as Service
|
|
let service: Service = try! container.resolve()
|
|
```
|
|
|
|
- seealso: `register(_:type:tag:factory:)`
|
|
*/
|
|
public func resolve<T>(tag: DependencyTagConvertible? = nil) throws -> T {
|
|
return try _resolve(tag: tag) { (factory: () throws -> T) in try factory() }
|
|
}
|
|
|
|
/**
|
|
Resolve an instance of requested type. Weakly-typed alternative of `resolve(tag:)`
|
|
|
|
- warning: This method does not make any type checks, so there is no guaranty that
|
|
resulting instance is actually an instance of requested type.
|
|
That can happen if you register forwarded type that is not implemented by resolved instance.
|
|
|
|
- parameters:
|
|
- type: Type to resolve
|
|
- tag: The arbitrary tag to use to lookup definition.
|
|
|
|
- throws: `DipError.DefinitionNotFound`, `DipError.AutoInjectionFailed`, `DipError.AmbiguousDefinitions`, `DipError.InvalidType`
|
|
|
|
- returns: An instance of requested type.
|
|
|
|
**Example**:
|
|
```swift
|
|
let service = try! container.resolve(Service.self) as! Service
|
|
let service = try! container.resolve(Service.self, tag: "service") as! Service
|
|
```
|
|
|
|
- seealso: `resolve(tag:)`, `register(_:type:tag:factory:)`
|
|
*/
|
|
public func resolve(_ type: Any.Type, tag: DependencyTagConvertible? = nil) throws -> Any {
|
|
return try resolve(type, tag: tag) { (factory: () throws -> Any) in try factory() }
|
|
}
|
|
|
|
/**
|
|
Resolve an instance of type `T` using generic builder closure that accepts generic factory and returns created instance.
|
|
|
|
- parameters:
|
|
- tag: The arbitrary tag to use to lookup definition.
|
|
- builder: Generic closure that accepts generic factory and returns instance created by that factory.
|
|
|
|
- throws: `DipError.DefinitionNotFound`, `DipError.AutoInjectionFailed`, `DipError.AmbiguousDefinitions`, `DipError.InvalidType`
|
|
|
|
- returns: An instance of type `T`.
|
|
|
|
- note: You _should not_ call this method directly, instead call any of other
|
|
`resolve(tag:)` or `resolve(tag:withArguments:)` methods.
|
|
You _should_ use this method only to resolve dependency with more runtime arguments than
|
|
_Dip_ supports (currently it's up to six) like in the following example:
|
|
|
|
```swift
|
|
public func resolve<T, A, B, C, ...>(tag: Tag? = nil, _ arg1: A, _ arg2: B, _ arg3: C, ...) throws -> T {
|
|
return try resolve(tag: tag) { factory in factory(arg1, arg2, arg3, ...) }
|
|
}
|
|
```
|
|
|
|
Though before you do so you should probably review your design and try to reduce the number of dependencies.
|
|
*/
|
|
public func resolve<T, U>(tag: DependencyTagConvertible? = nil, builder: ((U) throws -> T) throws -> T) throws -> T {
|
|
return try _resolve(tag: tag, builder: builder)
|
|
}
|
|
|
|
public func resolve<T>(tag: DependencyTagConvertible? = nil, builder: (() throws -> T) throws -> T) throws -> T {
|
|
return try _resolve(tag: tag, builder: builder)
|
|
}
|
|
|
|
/**
|
|
Resolve an instance of provided type using builder closure. Weakly-typed alternative of `resolve(tag:builder:)`
|
|
|
|
- seealso: `resolve(tag:builder:)`
|
|
*/
|
|
public func resolve<U>(_ type: Any.Type, tag: DependencyTagConvertible? = nil, builder: ((U) throws -> Any) throws -> Any) throws -> Any {
|
|
return try _resolve(type: type, tag: tag, builder: builder)
|
|
}
|
|
|
|
public func resolve(_ type: Any.Type, tag: DependencyTagConvertible? = nil, builder: (() throws -> Any) throws -> Any) throws -> Any {
|
|
return try _resolve(type: type, tag: tag, builder: builder)
|
|
}
|
|
|
|
}
|
|
|
|
extension DependencyContainer {
|
|
|
|
func _resolve<T>(tag aTag: DependencyTagConvertible? = nil, builder: (() throws -> T) throws -> T) throws -> T {
|
|
return try resolve(T.self, tag: aTag, builder: { factory in
|
|
try withoutActuallyEscaping(factory, do: { (factory) throws -> T in
|
|
try builder({ try factory() as! T })
|
|
})
|
|
}) as! T
|
|
}
|
|
|
|
func _resolve(type aType: Any.Type, tag: DependencyTagConvertible? = nil, builder: (() throws -> Any) throws -> Any) throws -> Any {
|
|
let key = DefinitionKey(type: aType, typeOfArguments: Void.self, tag: tag?.dependencyTag)
|
|
|
|
return try inContext(key:key, injectedInType: context?.resolvingType) {
|
|
try self._resolve(key: key, builder: { definition in
|
|
try builder { try definition.weakFactory(()) }
|
|
})
|
|
}
|
|
}
|
|
|
|
func _resolve<T, U>(tag aTag: DependencyTagConvertible? = nil, builder: ((U) throws -> T) throws -> T) throws -> T {
|
|
return try resolve(T.self, tag: aTag, builder: { factory in
|
|
try withoutActuallyEscaping(factory, do: { (factory) throws -> T in
|
|
try builder({ try factory($0) as! T })
|
|
})
|
|
}) as! T
|
|
}
|
|
|
|
func _resolve<U>(type aType: Any.Type, tag: DependencyTagConvertible? = nil, builder: ((U) throws -> Any) throws -> Any) throws -> Any {
|
|
let key = DefinitionKey(type: aType, typeOfArguments: U.self, tag: tag?.dependencyTag)
|
|
|
|
return try inContext(key:key, injectedInType: context?.resolvingType) {
|
|
try self._resolve(key: key, builder: { definition in
|
|
try builder(definition.weakFactory)
|
|
})
|
|
}
|
|
}
|
|
|
|
/// Lookup definition by the key and use it to resolve instance. Fallback to the key with `nil` tag.
|
|
func _resolve<T>(key aKey: DefinitionKey, builder: (_Definition) throws -> T) throws -> T {
|
|
guard let matching = self.definition(matching: aKey) else {
|
|
do {
|
|
return try autowire(key: aKey)
|
|
} catch {
|
|
if let resolved = collaboratingResolve(key: aKey, builder: builder) {
|
|
return resolved
|
|
} else {
|
|
throw error
|
|
}
|
|
}
|
|
}
|
|
|
|
let (key, definition) = matching
|
|
|
|
//first search for already resolved instance for this type or any of forwarding types
|
|
if let previouslyResolved: T = previouslyResolved(for: definition, key: key) {
|
|
log(level: .Verbose, "Reusing previously resolved instance \(previouslyResolved)")
|
|
return previouslyResolved
|
|
}
|
|
|
|
if let context = context { log(level: .Verbose, context) }
|
|
var resolvedInstance = try builder(definition)
|
|
|
|
/*
|
|
Strongly-typed `resolve(tag:builder:)` calls weakly-typed `resolve(_:tag:builder:)`,
|
|
so `T` will be `Any` at runtime, erasing type information when this method returns.
|
|
When we try to cast result of `Any` to generic type T Swift fails to cast it.
|
|
The same happens in the following code snippet:
|
|
|
|
let optService: Service? = ServiceImp()
|
|
let anyService: Any = optService
|
|
let service: Service = anyService as! Service
|
|
|
|
That happens because when Optional is casted to Any Swift can not implicitly unwrap it with as operator.
|
|
As a workaround we detect boxing here and unwrap it so that we return not a box, but wrapped instance.
|
|
*/
|
|
if let box = resolvedInstance as? BoxType, let unboxedAny = box.unboxed, let unboxed = unboxedAny as? T {
|
|
resolvedInstance = unboxed
|
|
}
|
|
|
|
//when builder calls factory it will in turn resolve sub-dependencies (if there are any)
|
|
//when it returns instance that we try to resolve here can be already resolved
|
|
//so we return it, throwing away instance created by previous call to builder
|
|
if let previouslyResolved: T = previouslyResolved(for: definition, key: key) {
|
|
log(level: .Verbose, "Reusing previously resolved instance \(previouslyResolved)")
|
|
return previouslyResolved
|
|
}
|
|
|
|
resolvedInstances[key: key, inScope: definition.scope, context: context] = resolvedInstance
|
|
|
|
if let resolvable = resolvedInstance as? Resolvable {
|
|
resolvedInstances.resolvableInstances.append(resolvable)
|
|
resolvable.resolveDependencies(self)
|
|
}
|
|
|
|
let shouldAutoInject = definition.autoInjectProperties ?? self.autoInjectProperties
|
|
if shouldAutoInject {
|
|
try autoInjectProperties(in: resolvedInstance)
|
|
}
|
|
try definition.resolveProperties(of: resolvedInstance, container: self)
|
|
|
|
log(level: .Verbose, "Resolved type \(key.type) with \(resolvedInstance)")
|
|
return resolvedInstance
|
|
}
|
|
|
|
private func previouslyResolved<T>(for definition: _Definition, key: DefinitionKey) -> T? {
|
|
//first check if exact key was already resolved
|
|
if let previouslyResolved: T = resolvedInstances[key: key, inScope: definition.scope, context: context] {
|
|
return previouslyResolved
|
|
}
|
|
//then check if any related type was already resolved
|
|
let keys = definition.implementingTypes.map({
|
|
DefinitionKey(type: $0, typeOfArguments: key.typeOfArguments, tag: key.tag)
|
|
})
|
|
for key in keys {
|
|
if let previouslyResolved: T = resolvedInstances[key: key, inScope: definition.scope, context: context] {
|
|
return previouslyResolved
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
/// Searches for definition that matches provided key
|
|
func definition(matching key: DefinitionKey) -> KeyDefinitionPair? {
|
|
if let definition = (self.definitions[key] ?? self.definitions[key.tagged(with: nil)]) {
|
|
return (key, definition)
|
|
}
|
|
|
|
//if no definition registered for exact type try to find type-forwarding definition that can resolve the type
|
|
//that will actually happen only when resolving optionals
|
|
if definitions.filter({ $0.0.type == key.type }).isEmpty {
|
|
return typeForwardingDefinition(forKey: key)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
}
|
|
|
|
///Pool to hold instances, created during call to `resolve()`.
|
|
///Before `resolve()` returns pool is drained.
|
|
class ResolvedInstances {
|
|
|
|
var resolvedInstances = [DefinitionKey: Any]()
|
|
var resolvableInstances = [Resolvable]()
|
|
|
|
//singletons are stored using reference type wrapper to be able to share them between containers
|
|
var sharedSingletonsBox = Box<[DefinitionKey: Any]>([:])
|
|
var sharedSingletons: [DefinitionKey: Any] {
|
|
get { return sharedSingletonsBox.unboxed }
|
|
set { sharedSingletonsBox.unboxed = newValue }
|
|
}
|
|
var singletons = [DefinitionKey: Any]()
|
|
|
|
var sharedWeakSingletonsBox = Box<[DefinitionKey: Any]>([:])
|
|
var sharedWeakSingletons: [DefinitionKey: Any] {
|
|
get { return sharedWeakSingletonsBox.unboxed }
|
|
set { sharedWeakSingletonsBox.unboxed = newValue }
|
|
}
|
|
var weakSingletons = [DefinitionKey: Any]()
|
|
|
|
subscript<T>(key key: DefinitionKey, inScope scope: ComponentScope, context context: DependencyContainer.Context) -> T? {
|
|
get {
|
|
let instance: Any?
|
|
switch scope {
|
|
case .singleton, .eagerSingleton:
|
|
instance = context.inCollaboration ? sharedSingletons[key] : singletons[key]
|
|
case .weakSingleton:
|
|
let singletons = context.inCollaboration ? sharedWeakSingletons : weakSingletons
|
|
if let boxed = singletons[key] as? WeakBoxType { instance = boxed.unboxed }
|
|
else { instance = singletons[key] }
|
|
case .shared:
|
|
instance = resolvedInstances[key]
|
|
case .unique:
|
|
return nil
|
|
}
|
|
return instance.flatMap { $0 as? T }
|
|
}
|
|
set {
|
|
switch scope {
|
|
case .singleton, .eagerSingleton:
|
|
sharedSingletons[key] = newValue
|
|
singletons[key] = newValue
|
|
case .weakSingleton:
|
|
sharedWeakSingletons[key] = newValue
|
|
weakSingletons[key] = newValue
|
|
case .shared:
|
|
resolvedInstances[key] = newValue
|
|
case .unique:
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
//MARK: - Resolvable
|
|
|
|
/// Resolvable protocol provides some extension points for resolving dependencies with property injection.
|
|
public protocol Resolvable {
|
|
/// This method will be called right after instance is created by the container.
|
|
func resolveDependencies(_ container: DependencyContainer)
|
|
/// This method will be called when all dependencies of the instance are resolved.
|
|
/// When resolving objects graph the last resolved instance will receive this callback first.
|
|
func didResolveDependencies()
|
|
}
|
|
|
|
extension Resolvable {
|
|
func resolveDependencies(_ container: DependencyContainer) {}
|
|
func didResolveDependencies() {}
|
|
}
|