闽公网安备 35020302035485号
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 了半天才发现。支持懒加载:有些依赖可能很重,可以等真正需要时才初始化