6-2.【架构设计】MVVM中ViewModel是否应该持有Model?若ViewModel很重、Model很大,如何避免内存+ARC+性能问题?

1 阅读3分钟

一、ViewModel 是否应该持有 Model?

答案:通常是持有引用,但方式要注意

1️⃣ 理论

  • ViewModel 的职责:

    • 将 Model 的数据转换成 View 可直接绑定的数据(格式化 / 排序 /过滤)
    • 处理 View 的用户交互,通知 Model 或服务层
  • Model 的存在是 ViewModel 的前提

    • ViewModel 需要 Model 的状态才能生成 UI
    • ViewModel 处理的通常是 Model 的快照或部分状态,而不是整份 Model

⚠️ 核心原则:持有是为了“只读/转换”,不要让 ViewModel 拥有 Model 的生命周期或复杂业务逻辑


2️⃣ 持有方式

  1. 弱引用 / unowned

    • 当 Model 的生命周期高于 ViewModel 时可用
    • 避免循环引用
  2. 不可变拷贝(struct)

    • 对于轻量级 Model(如 DTO、基本数据)
    • ViewModel 持有值类型拷贝,不会影响 Model 的原始状态
    • Swift 中 struct 的拷贝开销一般很小,适合中小型数据
  3. 只持有标识 / 数据流式订阅

    • 对于大型 Model 或数据库对象(CoreData / Realm / Firebase)
    • ViewModel 只持有 ID / 查询条件
    • 通过绑定 / Combine / RxSwift 获取 Model 数据
    • 避免一次性加载整个 Model,节省内存

二、ViewModel 很重、Model 很大时的问题

问题核心:ARC + 内存 + 数据更新频率

  1. ARC 循环引用

    • 如果 View 持有 ViewModel(通常是 strong)
    • ViewModel 又 strong 持有 Model(Model 可能持有闭包 / Delegate 回 ViewModel)
    • 循环引用 → 内存泄漏
  2. 大 Model 拷贝成本

    • struct/值拷贝每次都会复制内存
    • 大数组 / 图像 / JSON 数据 → 内存占用高,性能下降
  3. 频繁更新导致 UI 卡顿

    • ViewModel 持有大量数据,每次改变都触发 @Published 或 KVO 通知
    • 大量数据绑定 → 主线程压力大

三、优化方案(避免内存+性能问题)

1️⃣ 弱化或分离引用

class ViewModel {
    weak var model: Model?  // 避免循环引用
}
  • 如果 Model 持有 VC / VM 的引用,必须用 weak/unowned
  • 对 CoreData / Realm 对象,可用 ID 或查询

2️⃣ 用轻量快照替代整 Model

struct UserSnapshot {
    let id: String
    let name: String
    let avatarURL: URL
}

class UserViewModel: ObservableObject {
    @Published var user: UserSnapshot
    init(user: User) {
        self.user = UserSnapshot(id: user.id, name: user.name, avatarURL: user.avatarURL)
    }
}
  • ViewModel 持有快照而不是完整 Model
  • 大数据量只传递必要字段
  • 内存占用降低,ARC 压力小

3️⃣ 数据流式订阅

  • 对于大型 Model,ViewModel 不直接持有,而是通过 Publisher / Combine / RxSwift 订阅数据
class UserViewModel: ObservableObject {
    @Published var name: String = ""
    private var cancellables = Set<AnyCancellable>()

    init(userPublisher: AnyPublisher<User, Never>) {
        userPublisher
            .map { $0.name }
            .assign(to: &$name)
    }
}
  • 好处:

    • 不拷贝整个 Model
    • 自动响应数据变化
    • 内存占用稳定

4️⃣ 按需加载 & 分页

  • 大数组 / 大列表不要一次性放进 ViewModel
  • 使用分页或懒加载
  • 与 SwiftUI / UITableView / UICollectionView 的 diffable 数据源结合,减少内存占用

5️⃣ 避免频繁对象创建

  • ViewModel 中尽量复用对象,不要每次刷新数据就重建 ViewModel 或数组
  • 对于不可变数据,使用 let 避免 ARC 额外 retain/release 开销

✅ 总结策略

场景ViewModel 持有方式优化方法
小型 Model / DTO值类型拷贝直接持有 struct
中型 Model / 对象强引用 / 弱引用弱引用 + Snapshot
大型 Model / DB 对象标识 + 流式订阅Combine / RxSwift + 懒加载 / 分页
循环引用风险不可避免使用 weak/unowned
UI 频繁刷新任意大小 Model按需通知 / diffable 数据源

总结一句话:

ViewModel 可以持有 Model,但不要持有整个重量级对象;最好持有快照或标识,并结合流式订阅和懒加载来避免 ARC/内存/性能问题。