Simple DI Container

Simple dependency injection container with @Injected property wrapper.

← Back
Language: swift | Tags: dependency-injection di design-pattern testing
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