ZcashLightClientKit/Sources/ZcashLightClientKit/Utils/DIContainer.swift

87 lines
3.5 KiB
Swift

//
// DIContainer.swift
//
//
// Created by Michal Fousek on 01.05.2023.
//
import Foundation
/// This class represents depedency injection containers.
class DIContainer {
/// Structure that represents one registered dependency.
struct Dependency {
/// Closure which creates instance of the dependency.
let factory: (DIContainer) -> Any
/// Indicates if dependency is singleton. If this is `true` then `DIContainer` creates only one instance of this dependency and returns
/// it every time `resolve()` for the dependency is called.
let isSingleton: Bool
/// If the dependency is singleton then instance is stored here.
let instance: Any?
}
/// If this is `true` then `mockedDependencies` is used first to resolve dependencies. This is used for mocking in tests.
var isTestEnvironment = false
private let lock = NSRecursiveLock()
/// Dependencies are stored here.
private var dependencies: [String: Dependency] = [:]
/// Mocked dependencies are stored here.
private var mockedDependencies: [String: Dependency] = [:]
init() { }
private func key<T>(for type: T.Type) -> String {
return String(describing: T.self)
}
func register<T>(type: T.Type, isSingleton: Bool, factory: @escaping (DIContainer) -> T) {
lock.lock()
let key = self.key(for: type)
let depedency = Dependency(factory: factory, isSingleton: isSingleton, instance: nil)
dependencies[key] = depedency
lock.unlock()
}
func mock<T>(type: T.Type, isSingleton: Bool, factory: @escaping (DIContainer) -> T) {
lock.lock()
let key = self.key(for: type)
let depedency = Dependency(factory: factory, isSingleton: isSingleton, instance: nil)
mockedDependencies[key] = depedency
lock.unlock()
}
func resolve<T>(_ type: T.Type) -> T {
lock.lock()
defer { lock.unlock() }
let key = self.key(for: type)
let possibleDependency = (isTestEnvironment ? mockedDependencies[key] : dependencies[key]) ?? dependencies[key]
guard let dependency = possibleDependency else {
// When dependency is resolved before it's registered then the app crashes. It would be possible to not crash and throw some error here.
// But it complicates the rest of the code. And maybe it doesn't make sense because this kind of error is not recoverable. It can be fixed
// only by updating the code.
fatalError("Doesn't have registered dependency for type \(type).")
}
let instance: Any
if dependency.isSingleton, let singleton = dependency.instance {
instance = singleton
} else {
instance = dependency.factory(self)
}
if dependency.isSingleton && dependency.instance == nil {
dependencies[key] = Dependency(factory: dependency.factory, isSingleton: dependency.isSingleton, instance: instance)
}
guard let instance = instance as? T else {
// When dependency is resolved but instance of created depedency is different than expected type the app crashes. It would be possible to
// not crash and throw some error here. But it complicates the rest of the code. And maybe it doesn't make sense because this kind of
// error is not recoverable. It can be fixed only by updating the code.
fatalError("Getting dependency for type \(type) but created instance is \(instance)")
}
return instance
}
}