如何将现有ViewModel改造为WeakViewModel与SingletonViewModel并简化样板代码?
简化Weak引用ViewModel的懒加载与自动销毁样板代码
现有实现中每个ViewModel都要重复编写弱引用单例、ID跟踪、初始化/销毁打印等逻辑,导致样板代码冗余。可以通过基类+协议扩展的方式抽离通用逻辑,让子类只需聚焦业务代码。
1. 通用弱引用包装器与单例协议扩展
先实现用于存储弱引用的包装类,以及通用的单例核心逻辑:
// 弱引用包装器:解决关联对象无法直接存储weak变量的问题 private class WeakWrapper<T: AnyObject> { weak var value: T? init(value: T) { self.value = value } } private var sharedInstanceKey: UInt8 = 0 // 通用单例协议:约束子类需实现无参初始化 protocol WeakSingleton: AnyObject { init() } extension WeakSingleton where Self: ObservableObject { static var shared: Self { // 检查是否已有可用的弱引用实例 if let wrapper = objc_getAssociatedObject(self, &sharedInstanceKey) as? WeakWrapper<Self>, let instance = wrapper.value { return instance } // 实例不存在则创建并存储弱引用 let newInstance = Self() objc_setAssociatedObject(self, &sharedInstanceKey, WeakWrapper(value: newInstance), .OBJC_ASSOCIATION_RETAIN_NONATOMIC) return newInstance } }
2. 创建ViewModel基类
将ID管理、初始化/销毁打印等通用逻辑抽离到基类:
class WeakViewModel: ObservableObject, WeakSingleton { let id: UUID init(id: UUID = UUID()) { self.id = id printIfDebug("\(type(of: self)) \(#function) \(id)") } deinit { printIfDebug("\(type(of: self)) \(#function) \(id)") } // 满足WeakSingleton协议要求的无参初始化 convenience init() { self.init(id: UUID()) } }
3. 子类ViewModel简化实现
现在子类只需继承WeakViewModel,专注定义业务属性与自定义初始化:
final class App1FirstViewModel: WeakViewModel { @Published var counter: Int // 自定义带参数的初始化 init(counter: Int = 0, id: UUID = UUID()) { self.counter = counter super.init(id: id) } // 可选:重载带参数的shared方法(仅首次创建时使用参数,后续返回已有实例) static func shared(counter: Int = 0) -> App1FirstViewModel { if let wrapper = objc_getAssociatedObject(self, &sharedInstanceKey) as? WeakWrapper<App1FirstViewModel>, let instance = wrapper.value { return instance } let newInstance = App1FirstViewModel(counter: counter) objc_setAssociatedObject(self, &sharedInstanceKey, WeakWrapper(value: newInstance), .OBJC_ASSOCIATION_RETAIN_NONATOMIC) return newInstance } }
效果说明
- 子类无需重复编写
_shared弱引用变量、通用shared逻辑、ID属性以及初始化/销毁打印代码。 - 当没有页面持有ViewModel时,弱引用会自动释放实例,触发
deinit。 - 若需要带参数的单例初始化,只需重载
shared(counter:)方法即可,逻辑与原代码保持一致。
内容的提问来源于stack exchange,提问作者Jason Rich Darmawan
相关产品推荐
相关产品推荐

