SwiftUI列表行删除时Row View/ViewModel未触发deinit问题
SwiftUI List行视图与ViewModel生命周期异常问题解析
核心问题根源
你遇到的现象本质是错误使用@State管理引用类型ViewModel,加上SwiftUI List的视图复用机制共同导致的:
@State是为值类型设计的属性包装器,用来存储视图内部的可变值类型状态。把引用类型(RowViewModel)放在@State里,SwiftUI无法正确追踪其生命周期,会导致对象被意外持有,无法及时释放。- List内部有视图复用池,启动时的两次初始化是List在布局阶段创建临时视图进行预渲染,随后销毁临时实例,保留实际显示的视图——这属于SwiftUI的正常内部机制,但因为
@State的错误使用,放大了这个现象。 - 删除行时,由于
@State持有旧的ViewModel实例,加上List的视图重建逻辑,旧实例无法被ARC及时回收,直到App进程终止才会触发deinit。
修复方案
只需要把RowView中管理ViewModel的属性包装器从@State改成@StateObject(专门用于管理Observable引用类型的生命周期),同时优化删除逻辑:
修改后的完整代码:
struct ContentView: View { @State var list: [String] = ["A", "B", "C"] var body: some View { List { ForEach(list, id: \.self) { item in RowView(item: item) } .onDelete(perform: { indexSet in list.remove(atOffsets: indexSet) // 更简洁的删除写法 }) } } } struct RowView: View { @StateObject var vm: RowViewModel // 替换@State为@StateObject init(item: String) { _vm = StateObject(wrappedValue: RowViewModel(item: item)) // 显式初始化StateObject } var body: some View { Text(vm.item) } } @Observable class RowViewModel { public let item: String init(item: String) { print("RowVM:Init \(item)") self.item = item } deinit { print("RowVM:Deinit \(item)") } }
验证结果
- 启动App时,控制台输出(每个ViewModel仅初始化一次):
RowVM:Init A RowVM:Init B RowVM:Init C - 滑动删除行C时,控制台立即输出:
RowVM:Deinit C - 删除行B时,输出:
RowVM:Deinit B - 停止App时,剩余ViewModel正常释放:
RowVM:Deinit A
补充说明
@StateObject会在视图首次初始化时创建Observable对象,并在视图被销毁时自动释放对象,完美匹配ViewModel的生命周期需求。- 如果需要从父视图传递ViewModel,应该使用
@ObservedObject,但这里RowView是独立创建ViewModel,@StateObject是正确选择。
内容的提问来源于stack exchange,提问作者swat
相关产品推荐
相关产品推荐

