Pod/Classes/RuntimeArguments.swift
//
// Kraken
//
// Copyright (c) 2016 Syed Sabir Salman-Al-Musawi <sabirvirtuoso@gmail.com>
//
// 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.
//
import Foundation
/// MARK:- Custom Dependency Container (Dependency Factory registration with runtime arguments)
extension Kraken {
public static func register(_ interface: Any, tag: DependencyTagConvertible? = nil, scope: DependencyScope = .prototype, factory: () -> Injectable?, completionHandler: ((_ resolvedInstance: Injectable) -> ())? = nil) {
let definitionKey = prepareDefinitionKey(forInterface: interface, andTag: tag)
registerFactory(interface, tag: tag, scope: scope, factory: factory, completionHandler: completionHandler)
switch scope {
case .eagerSingleton: singletons[definitionKey] = factory()!
case .singleton: break
case .prototype: break
}
}
public static func register<Arg1>(_ interface: Any, tag: DependencyTagConvertible? = nil, scope: DependencyScope = .prototype, factory: @escaping (Arg1) -> Injectable?) throws {
if definitionExists(forKey: String(describing: Arg1.self)) {
registerAutoWiringFactory(interface, scope: scope, numberOfArguments: 1) { () throws -> Injectable? in
try factory(Kraken.inject(Arg1.self) as! Arg1)
}
return
}
try verifyScope(interface, tag: tag, scope: scope)
registerFactory(interface, tag: tag, scope: scope, factory: factory, numberOfArguments: 1)
}
public static func register<Arg1, Arg2>(_ interface: Any, tag: DependencyTagConvertible? = nil, scope: DependencyScope = .prototype, factory: @escaping (Arg1, Arg2) -> Injectable?) throws {
if definitionExists(forKey: String(describing: Arg1.self)) && definitionExists(forKey: String(describing: Arg2.self)) {
registerAutoWiringFactory(interface, scope: scope, numberOfArguments: 2) { () throws -> Injectable? in
try factory(Kraken.inject(Arg1.self) as! Arg1, Kraken.inject(Arg2.self) as! Arg2)
}
return
}
try verifyScope(interface, tag: tag, scope: scope)
registerFactory(interface, tag: tag, scope: scope, factory: factory, numberOfArguments: 2)
}
public static func register<Arg1, Arg2, Arg3>(_ interface: Any, tag: DependencyTagConvertible? = nil, scope: DependencyScope = .prototype, factory: @escaping (Arg1, Arg2, Arg3) -> Injectable?) throws {
if definitionExists(forKey: String(describing: Arg1.self)) && definitionExists(forKey: String(describing: Arg2.self)) && definitionExists(forKey: String(describing: Arg3.self)) {
registerAutoWiringFactory(interface, scope: scope, numberOfArguments: 3) { () throws -> Injectable? in
try factory(Kraken.inject(Arg1.self) as! Arg1, Kraken.inject(Arg2.self) as! Arg2, Kraken.inject(Arg3.self) as! Arg3)
}
return
}
try verifyScope(interface, tag: tag, scope: scope)
registerFactory(interface, tag: tag, scope: scope, factory: factory, numberOfArguments: 3)
}
fileprivate static func verifyScope(_ interface: Any, tag: DependencyTagConvertible?, scope: DependencyScope) throws {
let definitionKey = prepareDefinitionKey(forInterface: interface, andTag: tag)
switch scope {
case .eagerSingleton: throw KrakenError.eagerSingletonNotAllowed(key: definitionKey)
case .singleton: break
case .prototype: break
}
}
fileprivate static func registerFactory<F>(_ interface: Any, tag: DependencyTagConvertible?, scope: DependencyScope, factory: F, numberOfArguments: Int = 0, completionHandler: ((Injectable) -> ())? = nil) {
let definitionKey = prepareDefinitionKey(forInterface: interface, andTag: tag)
definitionMap[definitionKey] = FactoryDefinition(scope: scope, factory: factory, numberOfArguments: numberOfArguments, completionHandler: completionHandler)
}
fileprivate static func registerAutoWiringFactory(_ interface: Any, scope: DependencyScope, numberOfArguments: Int = 0, autoWiringFactory: @escaping () throws -> Injectable?) {
let definitionKey = String(describing: interface)
let dependencydefinition = DependencyDefinition(scope: scope, numberOfArguments: 3)
dependencydefinition.autoWiringFactory = autoWiringFactory
definitionMap[definitionKey] = dependencydefinition
}
}
/// MARK:- Custom Dependency Container (Dependency injection implementation with runtime arguments)
extension Kraken {
public static func inject<Arg1>(_ typeToInject: Any, tag: DependencyTagConvertible? = nil, withArguments arg1: Arg1) throws -> Injectable? {
return try resolveFactory(typeToInject, tag: tag) { (factory: (Arg1) -> Injectable?) in
factory(arg1)
}
}
public static func inject<Arg1, Arg2>(_ typeToInject: Any, tag: DependencyTagConvertible? = nil, withArguments arg1: Arg1, _ arg2: Arg2) throws -> Injectable? {
return try resolveFactory(typeToInject, tag: tag) { (factory: (Arg1, Arg2) -> Injectable?) in
factory(arg1, arg2)
}
}
public static func inject<Arg1, Arg2, Arg3>(_ typeToInject: Any, tag: DependencyTagConvertible? = nil, withArguments arg1: Arg1, _ arg2: Arg2, _ arg3: Arg3) throws -> Injectable? {
return try resolveFactory(typeToInject, tag: tag) { (factory: (Arg1, Arg2, Arg3) -> Injectable?) in
factory(arg1, arg2, arg3)
}
}
public static func resolveFactory<F>(_ typeToInject: Any, tag: DependencyTagConvertible?, builder: @escaping (F) -> Injectable?) throws -> Injectable? {
let definitionKey = prepareDefinitionKey(forInterface: typeToInject, andTag: tag)
let factoryDefinition = try verifyAndReturnFactoryDefinition(typeToInject, tag: tag, builder: builder)
switch factoryDefinition.scope {
case .singleton: return singletonInstance(definitionKey, factoryDefinition: factoryDefinition, builder: builder)
case .prototype: return builder(factoryDefinition.factory)
default: return nil
}
}
fileprivate static func verifyAndReturnFactoryDefinition<F>(_ typeToInject: Any, tag: DependencyTagConvertible?, builder: (F) -> Injectable?) throws -> FactoryDefinition<F> {
let definitionKey = prepareDefinitionKey(forInterface: typeToInject, andTag: tag)
guard definitionExists(forKey: definitionKey) else {
throw KrakenError.definitionNotFound(key: definitionKey)
}
let dependencyDefinition = definitionMap[definitionKey]
guard let factoryDefinition = dependencyDefinition as? FactoryDefinition<F> else {
if dependencyDefinition!.numberOfArguments == 0 {
throw KrakenError.factoryNotFound(key: definitionKey)
}
throw KrakenError.argumentCountNotMatched(key: definitionKey)
}
return factoryDefinition
}
fileprivate static func singletonInstance<F>(_ definitionKey: String, factoryDefinition: FactoryDefinition<F>, builder: @escaping (F) -> Injectable?) -> Injectable? {
synchronized(Kraken.self) {
if singletons[definitionKey] == nil {
singletons[definitionKey] = builder(factoryDefinition.factory)!
}
}
return singletons[definitionKey]
}
}
/// MARK:- Global functions for injecting generic types with 1, 2 and 3 runtime arguments respectively
public func inject<Arg1, T>(_ typeToInject: T.Type, tag: DependencyTagConvertible? = nil, withArguments arg1: Arg1) -> T where T: Any {
return try! Kraken.inject(typeToInject, tag: tag, withArguments: arg1) as! T
}
public func inject<Arg1, Arg2, T>(_ typeToInject: T.Type, tag: DependencyTagConvertible? = nil, withArguments arg1: Arg1, _ arg2: Arg2) -> T where T: Any {
return try! Kraken.inject(typeToInject, tag: tag, withArguments: arg1, arg2) as! T
}
public func inject<Arg1, Arg2, Arg3, T>(_ typeToInject: T.Type, tag: DependencyTagConvertible? = nil, withArguments arg1: Arg1, _ arg2: Arg2, _ arg3: Arg3) -> T where T: Any {
return try! Kraken.inject(typeToInject, tag: tag, withArguments: arg1, arg2, arg3) as! T
}