You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何将现有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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.06 07:43:22