Last active
March 17, 2020 10:51
-
-
Save CassiusPacheco/951edf2fe4c68c08cce539867d3340fe to your computer and use it in GitHub Desktop.
Dependency Container
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
import Foundation | |
extension NSLocking { | |
@discardableResult | |
public func sync<T>(action: () -> T) -> T { | |
lock() | |
defer { self.unlock() } | |
return action() | |
} | |
} | |
final public class DependencyContainer { | |
private var factories = [String: Any]() | |
private var singletonFactories = [String: Any]() | |
private let lock: NSLocking | |
public init(lock: NSLocking = NSRecursiveLock()) { | |
self.lock = lock | |
} | |
/// Returns whether the specified type has been registered. It returns `true` if there's a singleton instance | |
/// already created, or for a singleton factory or regular factory registered. | |
/// - Note: This method is thread safe. | |
public func contains<T>(_ type: T.Type) -> Bool { | |
return lock.sync { | |
let key = String(describing: type) | |
return singletonFactories[key] != nil || factories[key] != nil | |
} | |
} | |
} | |
public extension DependencyContainer { | |
// MARK: - Singleton registration | |
/// Registers a singleton factory closure. The instance will only be created once resolved. | |
/// Arguments are not supported for singleton registration. | |
/// - Note: This method is not thread safe and should only be used at the application start up. | |
func registerSingleton<T>(_ type: T.Type, factory: @escaping ((DependencyContainer) -> T)) { | |
singletonFactories[String(describing: type)] = factory | |
} | |
} | |
public extension DependencyContainer { | |
// MARK: - Factory methods with no arguments | |
/// Registers a factory closure. A new instance will be created every time it is resolved. | |
/// - Note: This method is not thread safe and should only be used at the application start up. | |
func register<T>(_ type: T.Type, factory: @escaping ((DependencyContainer) -> T)) { | |
factories[String(describing: type)] = factory | |
} | |
/// For singleton registration, it returns a previously created singleton instance if already created, otherwise | |
/// creates a new instance and caches it. For regular factory, it returns a new instance value. | |
/// - Note: This method is thread safe. | |
func resolve<T>(_ type: T.Type) -> T { | |
return lock.sync { | |
let key = String(describing: type) | |
if let singletonFactory = self.singletonFactories[key] as? ((DependencyContainer) -> T) { | |
let instance = singletonFactory(self) | |
self.singletonFactories[key] = { (di: DependencyContainer) -> T in return instance } | |
return instance | |
} else if let instanceFactory = self.factories[key] as? ((DependencyContainer) -> T) { | |
return instanceFactory(self) | |
} else { | |
fatalError("Instance of type `\(type)` hasn't been registered") | |
} | |
} | |
} | |
} | |
public extension DependencyContainer { | |
// MARK: - Factory methods with one argument | |
/// Registers a factory closure with one argument. A new instance will be created every time it is resolved. | |
/// - Note: This method is not thread safe and should only be used at the application start up. | |
func register<T, A>(_ type: T.Type, factory: @escaping ((DependencyContainer, A) -> T)) { | |
factories[String(describing: type)] = factory | |
} | |
/// Returns a newly created instance injecting one argument. | |
/// - Note: This method is thread safe. | |
func resolve<T, A>(_ type: T.Type, argument: A) -> T { | |
return lock.sync { | |
if let instanceFactory = self.factories[String(describing: type)] as? ((DependencyContainer, A) -> T) { | |
return instanceFactory(self, argument) | |
} else { | |
fatalError("Instance of type `\(type)` hasn't been registered") | |
} | |
} | |
} | |
} | |
public extension DependencyContainer { | |
// MARK: - Factory methods with two arguments | |
/// Registers a factory closure with two arguments. A new instance will be created every time it is resolved. | |
/// - Note: This method is not thread safe and should only be used at the application start up. | |
func register<T, A, B>(_ type: T.Type, factory: @escaping ((DependencyContainer, A, B) -> T)) { | |
factories[String(describing: type)] = factory | |
} | |
/// Returns a newly created instance injecting two arguments. | |
/// - Note: This method is thread safe. | |
func resolve<T, A, B>(_ type: T.Type, arguments arg1: A, _ arg2: B) -> T { | |
return lock.sync { | |
if let instanceFactory = self.factories[String(describing: type)] as? ((DependencyContainer, A, B) -> T) { | |
return instanceFactory(self, arg1, arg2) | |
} else { | |
fatalError("Instance of type `\(type)` hasn't been registered") | |
} | |
} | |
} | |
} | |
public extension DependencyContainer { | |
// MARK: - Factory methods with three arguments | |
/// Registers a factory closure with three arguments. A new instance will be created every time it is resolved. | |
/// - Note: This method is not thread safe and should only be used at the application start up. | |
func register<T, A, B, C>(_ type: T.Type, factory: @escaping ((DependencyContainer, A, B, C) -> T)) { | |
factories[String(describing: type)] = factory | |
} | |
/// Returns a newly created instance injecting three arguments. | |
/// - Note: This method is thread safe. | |
func resolve<T, A, B, C>(_ type: T.Type, arguments arg1: A, _ arg2: B, _ arg3: C) -> T { | |
return lock.sync { | |
if let instanceFactory = self.factories[String(describing: type)] as? ((DependencyContainer, A, B, C) -> T) { | |
return instanceFactory(self, arg1, arg2, arg3) | |
} else { | |
fatalError("Instance of type `\(type)` hasn't been registered") | |
} | |
} | |
} | |
} | |
public extension DependencyContainer { | |
// MARK: - Factory methods with four arguments | |
/// Registers a factory closure with four arguments. A new instance will be created every time it is resolved. | |
/// - Note: This method is not thread safe and should only be used at the application start up. | |
func register<T, A, B, C, D>(_ type: T.Type, factory: @escaping ((DependencyContainer, A, B, C, D) -> T)) { | |
factories[String(describing: type)] = factory | |
} | |
/// Returns a newly created instance injecting four arguments. | |
/// - Note: This method is thread safe. | |
func resolve<T, A, B, C, D>(_ type: T.Type, arguments arg1: A, _ arg2: B, _ arg3: C, _ arg4: D) -> T { | |
return lock.sync { | |
if let instanceFactory = self.factories[String(describing: type)] as? ((DependencyContainer, A, B, C, D) -> T) { | |
return instanceFactory(self, arg1, arg2, arg3, arg4) | |
} else { | |
fatalError("Instance of type `\(type)` hasn't been registered") | |
} | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Unit Tests: