SwiftUI List引用已删除NSManagedObject致崩溃的解决方案问询
SwiftUI @FetchRequest 删除CoreData对象崩溃解决方案
问题表现
使用@FetchRequest填充List组件时,即便NSManagedObject实例已经从持久化存储中被删除(比如列表滑动删除操作触发),SwiftUI仍会尝试访问该已删除对象,最终引发应用崩溃。
该问题复现成本极低:使用Xcode提供的默认SwiftUI + CoreData模板新建项目,将ContentView代码替换为如下内容即可稳定复现:
struct ContentView: View { @Environment(\.managedObjectContext) private var moc @FetchRequest(sortDescriptors: []) private var items: FetchedResults<Item> var body: some View { List { ForEach(items) { item in ItemRow(item: item) } .onDelete { $0.map{items[$0]}.forEach(moc.delete) try! moc.save() } } .toolbar { Button("Add") { let newItem = Item(context: moc) newItem.timestamp = Date() try! moc.save() } } } } struct ItemRow: View { @ObservedObject var item: Item var body: some View { Text("\(item.timestamp!)") } }
运行项目后向列表添加若干条目,滑动删除任意一行应用就会立即崩溃,触发原因是ItemRow视图仍尝试使用已被删除的Item实例完成界面渲染。
常见方案的缺陷
目前流传的两类临时修复方案都存在明显问题:
- 子视图判断
!item.isFault后再渲染内容:未被删除的NSManagedObject在系统内存不足时也会被CoreData自动转为fault状态优化内存占用,该判断会导致正常展示的内容意外消失,引入非预期副作用。 - 将删除操作包裹在
viewContext.perform{}闭包中执行:实测该方案无法稳定避免崩溃,属于无效修复。
可靠无副作用修复方案
方案1:子视图判断对象上下文有效性(推荐)
不要用isFault做判断,改为判断对象是否仍绑定有效托管对象上下文:只有真正被删除的对象,其managedObjectContext属性才会被置为nil,正常触发fault的未删除对象始终会持有上下文引用,不会出现误判。
修改后的ItemRow代码如下:
struct ItemRow: View { @ObservedObject var item: Item var body: some View { if item.managedObjectContext != nil { Text("\(item.timestamp!)") } } }
该方案改动量极小,无任何副作用,适配所有iOS版本,是目前生产环境最稳定的修复方式。
方案2:调整删除操作执行时序
如果不想在每个子视图增加判断,可以将删除逻辑派发到主线程下一个RunLoop执行,给SwiftUI留出响应列表数据源变更的时间,在对应子视图被移除后再执行对象删除操作,从时序上避免访问已删除对象:
修改后的onDelete代码如下:
.onDelete { indexSet in DispatchQueue.main.async { indexSet.map{ items[$0] }.forEach(moc.delete) try? moc.save() } }
提示:生产环境不建议使用
try!强制解包保存操作,建议用do/catch包裹存储逻辑,捕获并处理可能的存储错误,避免额外崩溃。
内容的提问来源于stack exchange,提问作者Hundley
相关产品推荐
相关产品推荐

