87 lines
3.5 KiB
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
|
|
}
|
|
}
|