Simple DI Container
Simple dependency injection container with @Injected property wrapper.
final class DIContainer: @unchecked Sendable {
static let shared = DIContainer()
private var factories: [String: () -> Any] = [:]
private var singletons: [String: Any] = [:]
private let lock = NSLock()
private init() {}
func register<T>(_ type: T.Type, factory: @escaping () -> T) {
lock.withLock {
factories[String(describing: type)] = factory
}
}
func registerSingleton<T>(_ type: T.Type, factory: @escaping () -> T) {
lock.withLock {
factories[String(describing: type)] = factory
singletons[String(describing: type)] = nil
}
}
func resolve<T>(_ type: T.Type) -> T? {
lock.withLock {
let key = String(describing: type)
if let singleton = singletons[key] as? T { return singleton }
guard let factory = factories[key] else { return nil }
let instance = factory() as! T
if singletons.keys.contains(key) { singletons[key] = instance }
return instance
}
}
}
@propertyWrapper
struct Injected<T> {
var wrappedValue: T {
guard let value = DIContainer.shared.resolve(T.self) else {
fatalError("No registered dependency for \(String(describing: T.self))")
}
return value
}
}
// Usage:
// DIContainer.shared.register(APIClient.self) { APIClientImpl() }
// @Injected var apiClient: APIClient