https://www.avanderlee.com/swift/dependency-injection/ https://zenn.dev/yimajo/articles/e9f72549270873
https://zamzam.io/swift-dependency-injection-via-property-wrapper/
| // | |
| // AlphaApp.swift | |
| // Shared | |
| // | |
| // Created by damon.ahn on 2022/07/15. | |
| // | |
| import SwiftUI | |
| @main | |
| struct AlphaApp: App { | |
| var body: some Scene { | |
| WindowGroup { | |
| ContentView().task { | |
| // RegisterHelper().register() | |
| let sample = InjectSample() | |
| sample.register() | |
| sample.resolve() | |
| } | |
| } | |
| } | |
| } | |
| public class InjectSample { | |
| // @Inject1 | |
| // var aaaa: AAAAA | |
| @Inject1 | |
| var cas: CASProtocol | |
| @Inject2 | |
| var casfactory: CASFactory | |
| @Inject3(\.caslogFactory) | |
| var factory1: CasLogFactoryProtocol | |
| @Inject4(CasLogFactoryKey.self) | |
| var factory2: CasLogFactoryProtocol | |
| func register() { | |
| let dependencies = Dependencies { | |
| Module(CASProtocol.self) { CASLog() } | |
| Module(CASFactory.self) { CASFactory(CASFactoryImpl()) } | |
| } | |
| dependencies.build() | |
| Container.share | |
| .register(CasLogFactoryKey.self) { CasLogFactoryImpl () } | |
| } | |
| func resolve() { | |
| print("====== Inject1 ======") | |
| cas.printing() | |
| print("====== Inject2 ======") | |
| casfactory.printing() | |
| print("====== Inject3 ======") | |
| factory1.printing() | |
| print("====== Inject4 ======") | |
| factory2.printing() | |
| } | |
| } | |
| public protocol AAAAA {} | |
| public protocol CASProtocol: Injectable { | |
| func printing() | |
| } | |
| public struct CASLog: CASProtocol { | |
| public func printing() { | |
| print("Printing CASLog") | |
| } | |
| } | |
| public protocol CASFactoryProtocol { | |
| func printing() | |
| } | |
| public struct CASFactoryImpl: CASFactoryProtocol { | |
| public init() {} | |
| public func printing() { | |
| print("Printing CASFactoryImpl") | |
| } | |
| } | |
| public struct CASFactory: Injectable { | |
| public let factory: CASFactoryProtocol | |
| public init(_ factory: CASFactoryProtocol) { | |
| self.factory = factory | |
| } | |
| public func printing() { | |
| factory.printing() | |
| } | |
| } | |
| public protocol CasLogFactoryProtocol { | |
| func printing() | |
| } | |
| public struct CasLogFactoryImpl: CasLogFactoryProtocol { | |
| public func printing() { | |
| print("Printing CasLogFactoryImpl") | |
| } | |
| } | |
| public struct CasLogFactoryKey: InjectionKey { | |
| public typealias Value = CasLogFactoryProtocol | |
| public static var currentValue: Value { | |
| Container.share.resolve(Self.self) | |
| } | |
| } | |
| extension InjectedValues { | |
| var caslogFactory: CasLogFactoryProtocol { | |
| Self[CasLogFactoryKey.self] | |
| } | |
| } | |
| /// Resolves an instance from the dependency injection container. | |
| @propertyWrapper | |
| public class Inject1<Value> { | |
| private let name: String? | |
| private var storage: Value? | |
| public var wrappedValue: Value { | |
| storage ?? { | |
| let value: Value = Dependencies.root.resolve(for: name) | |
| storage = value // Reuse instance for later | |
| return value | |
| }() | |
| } | |
| public init() { | |
| self.name = nil | |
| } | |
| public init(_ name: String) { | |
| self.name = name | |
| } | |
| } | |
| @propertyWrapper | |
| public class Inject2<Value: Injectable> { | |
| private let name: String? | |
| private var storage: Value? | |
| public var wrappedValue: Value { | |
| storage ?? { | |
| let value: Value = Dependencies.root.resolve(for: name) | |
| storage = value // Reuse instance for later | |
| return value | |
| }() | |
| } | |
| public init() { | |
| self.name = nil | |
| } | |
| public init(_ name: String) { | |
| self.name = name | |
| } | |
| } | |
| public protocol Injectable {} | |
| /// A type that contributes to the object graph. | |
| public struct Module { | |
| fileprivate let name: String | |
| fileprivate let resolve: () -> Injectable | |
| public init(_ name: Any.Type, _ resolve: @escaping () -> Injectable) { | |
| self.name = String(describing: name) | |
| self.resolve = resolve | |
| } | |
| } | |
| /// A dependency collection that provides resolutions for object instances. | |
| public class Dependencies { | |
| /// Stored object instance factories. | |
| private var modules: [String: Module] = [:] | |
| public init() {} | |
| deinit { modules.removeAll() } | |
| } | |
| extension Dependencies { | |
| /// Registers a specific type and its instantiating factory. | |
| func add(module: Module) { | |
| modules[module.name] = module | |
| } | |
| /// Resolves through inference and returns an instance of the given type from the current default container. | |
| /// | |
| /// If the dependency is not found, an exception will occur. | |
| func resolve<T>(for name: String? = nil) -> T { | |
| let name = name ?? String(describing: T.self) | |
| guard let component: T = modules[name]?.resolve() as? T else { | |
| fatalError("Dependency '\(T.self)' not resolved!") | |
| } | |
| return component | |
| } | |
| /// Composition root container of dependencies. | |
| static var root = Dependencies() | |
| /// Construct dependency resolutions. | |
| public convenience init(@ModuleBuilder _ modules: () -> [Module]) { | |
| self.init() | |
| modules().forEach { add(module: $0) } | |
| } | |
| /// Construct dependency resolution. | |
| public convenience init(@ModuleBuilder _ module: () -> Module) { | |
| self.init() | |
| add(module: module()) | |
| } | |
| /// Assigns the current container to the composition root. | |
| public func build() { | |
| // Used later in property wrapper | |
| Self.root = self | |
| } | |
| /// DSL for declaring modules within the container dependency initializer. | |
| @resultBuilder public struct ModuleBuilder { | |
| public static func buildBlock(_ modules: Module...) -> [Module] { modules } | |
| public static func buildBlock(_ module: Module) -> Module { module } | |
| } | |
| } |
| import Foundation | |
| public protocol InjectionKey { | |
| associatedtype Value | |
| static var currentValue: Self.Value { get } | |
| } | |
| // MARK: - InjectedValues | |
| public struct InjectedValues { | |
| private static var current = InjectedValues() | |
| public static subscript<K>(key: K.Type) -> K.Value where K : InjectionKey { | |
| get { key.currentValue } | |
| } | |
| public static subscript<T>(_ keyPath: KeyPath<InjectedValues, T>) -> T { | |
| get { current[keyPath: keyPath] } | |
| } | |
| } | |
| @propertyWrapper | |
| public class Inject3<Value> { | |
| private let keyPath: KeyPath<InjectedValues, Value> | |
| private var storage: Value? | |
| public var wrappedValue: Value { | |
| storage ?? { | |
| let value: Value = InjectedValues[keyPath] | |
| storage = value // Reuse instance for later | |
| return value | |
| }() | |
| } | |
| public init(_ keyPath: KeyPath<InjectedValues, Value>) { | |
| self.keyPath = keyPath | |
| } | |
| } | |
| @propertyWrapper | |
| public class Inject4<Value> { | |
| private let lazyValue: (() -> Value) | |
| private var storage: Value? | |
| public var wrappedValue: Value { | |
| storage ?? { | |
| let value: Value = lazyValue() | |
| storage = value // Reuse instance for later | |
| return value | |
| }() | |
| } | |
| public init<K>(_ key: K.Type) where K : InjectionKey, Value == K.Value { | |
| lazyValue = { | |
| key.currentValue | |
| } | |
| } | |
| } | |
| public class Container { | |
| public static let share: Container = Container() | |
| var dict = [String: Any]() | |
| init() {} | |
| func register(_ name: Any.Type, instance: () -> Any) { | |
| let name = String(describing: name) | |
| let resolve = instance() | |
| dict[name] = resolve | |
| } | |
| func resolve<T>(_ name: Any.Type) -> T { | |
| let name = String(describing: name) | |
| guard let component = dict[name] as? T else { | |
| fatalError("Dependency '\(T.self)' not resolved!") | |
| } | |
| return component | |
| } | |
| } |