4.最好能简单易用:最好能像 SwiftUI 的 @State 那样方便
说干就干,我先设计了两个核心组件:2. @Injected - 一个属性包装器,让注入过程更加优雅
public class DIContainer { privatevar factories: [String: Any] = [:] func register<T>(_ type: T.Type, factory: @escaping () -> T) { let key = String(describing: type) factories[key] = factory } func resolve<T>(_ type: T.Type) -> T? { let key = String(describing: type) guardlet factory = factories[key] as? () -> Telse { returnnil } let instance = factory() return instance } }看起来是不是超级简单?我们就是用了个字典,key 是类型描述,value 是创建这个类型实例的工厂闭包。讲真,第一次实现的时候我还考虑过用更复杂的方案,比如用泛型参数作为 key,但发现 Swift 的类型系统在这方面有些局限,所以还是用了最朴素的字符串方式。
let container = DIContainer() container.register(NetworkServiceProtocol.self, factory: { NetworkService() }) // 堆代码 duidaima.com // 需要用的时候 let networkService = container.resolve(NetworkServiceProtocol.self)不过等等,这样每次调用 resolve() 都会创建一个新实例。对于网络服务这种常用组件,我们肯定不想每次都创建新的,更希望它是个单例。怎么办呢?
public enum DependencyLifetime { case singleton // 单例,只创建一次 case transient // 瞬时,每次解析都创建新的 }然后给我们的容器加上生命周期支持:
public class DIContainer { staticlet shared = DIContainer() // 容器本身是个单例 privatevar factories: [String: Any] = [:] privatevar singletons: [String: Any] = [:] // 存放单例实例 privatevar lifetimes: [String: DependencyLifetime] = [:] privateinit() {} // 私有初始化器,防止直接创建 func register<T>(_ type: T.Type, factory: @escaping () -> T, lifetime: DependencyLifetime) { let key = String(describing: type) factories[key] = factory lifetimes[key] = lifetime } func resolve<T>(_ type: T.Type) -> T? { let key = String(describing: type) guardlet lifetime = lifetimes[key] else { returnnil } switch lifetime { case .transient: // 临时创建,直接调用工厂方法 guardlet factory = factories[key] as? () -> Telse { returnnil } return factory() case .singleton: // 单例模式,先看缓存里有没有 iflet instance = singletons[key] as? T { return instance } // 没有就创建一个并缓存起来 guardlet factory = factories[key] as? () -> Telse { returnnil } let instance = factory() singletons[key] = instance return instance } } }实现起来也不复杂,就是加了个字典来缓存单例实例,解析时根据生命周期决定是返回缓存的还是创建新的。
container.register(NetworkServiceProtocol.self, factory: { NetworkService() }, lifetime: .singleton)用属性包装器让注入更优雅
@propertyWrapper publicstruct Injected<T> { privatevar dependency: T publicinit() { guardlet dependency = DIContainer.shared.resolve(T.self) else { fatalError("哎呀,找不到类型为 \(T.self) 的依赖,是不是忘记注册了?") } self.dependency = dependency } publicvar wrappedValue: T { return dependency } }有了这个属性包装器,注入依赖的代码就变得超级简洁了:
class UserService { @Injected var networkService: NetworkServiceProtocol // 直接用 networkService,不用管它是怎么来的 }是不是感觉清爽多了?属性包装器在访问时自动帮我们从容器中解析依赖,完全不需要手动调用 resolve()。这就是 Swift 语言的魅力所在,能让我们写出如此优雅的代码。
protocol NetworkServiceProtocol { func fetchData() -> String } class NetworkService: NetworkServiceProtocol { func fetchData() -> String { return"真实网络数据" } } // 顺便定义一个 Mock 版本,方便单元测试 class MockNetworkService: NetworkServiceProtocol { func fetchData() -> String { return"模拟网络数据" } } class UserService { @Injectedvar networkService: NetworkServiceProtocol func loadProfile() { let data = networkService.fetchData() print(data) } }然后,在 App 启动时初始化容器:
@main struct ExampleApp: App { init() { DIContainer.setup() } var body: some Scene { WindowGroup { ContentView() } } } extension DIContainer { staticfunc setup() { let container = DIContainer.shared // 注册服务,生产环境用 NetworkService container.register(NetworkServiceProtocol.self, factory: { NetworkService() }, lifetime: .singleton) // 其他服务 container.register(UserService.self, factory: { UserService() }, lifetime: .singleton) // 需要测试时,只需把上面注册改为 MockNetworkService 即可无缝切换 } }最后,在视图模型中使用这些服务:
@Observable class UserViewModel { // 这里有个小坑,要记得加 @ObservationIgnored @ObservationIgnored @Injected var userService: UserService // 你的业务逻辑... }提个醒: 在 SwiftUI 的 Observable 里使用依赖注入时,记得加 @ObservationIgnored,否则 SwiftUI 会把注入的依赖也当作状态追踪,可能导致不必要的视图刷新。我就因为忘了加这个,导致切换页面时出现了奇怪的性能问题,debug 了半天才发现。
支持懒加载:有些依赖可能很重,可以等真正需要时才初始化