如何避免删除CoreData最后一个实体时SwiftUI DetailView崩溃
CoreData实体删除导致SwiftUI应用崩溃问题
我有一个名为TestItem的CoreData实体:
当集合中只剩1个实体时,删除该实体会触发应用崩溃,错误信息为:Thread 1: EXC_BREAKPOINT (code=1, subcode=0x197c0b8b4)。问题出在DetailView通过@ObservedObject初始化后,其绑定的实体不能为nil,而删除最后一个实体时,无法提前销毁DetailView,导致已删除的实体仍被视图持有,进而引发崩溃。
复现代码
import SwiftUI import CoreData struct ContentView: View { let persistence = PersistenceController.shared @FetchRequest(entity: TestItem.entity(), sortDescriptors: [NSSortDescriptor(keyPath: \TestItem.date, ascending: false)]) var items: FetchedResults<TestItem> @State var selectedItem: TestItem? @State var selectedItemIndex: Int? var body: some View { NavigationSplitView { List(selection: $selectedItem) { ForEach(items) { item in VStack(alignment: .leading) { Text(item.name) .foregroundColor(selectedItem == item ? .blue : .white) } .onTapGesture { selectedItem = item selectedItemIndex = items.firstIndex(of: item) } } } .toolbar { Button(action: { addItem() }) { Image(systemName: "plus.square") } } } detail: { if let selectedItem = selectedItem { DetailView(item: selectedItem, onDelete: { guard !items.isEmpty else { self.selectedItem = nil selectedItemIndex = nil return } if selectedItemIndex! > items.indices.last! { selectedItemIndex = selectedItemIndex! - 1 } self.selectedItem = items[selectedItemIndex!] }) } } } private func addItem() { let newItem = TestItem(context: persistence.container.viewContext) newItem.name = "Test Item" newItem.date = Date() do { try persistence.container.viewContext.save() selectedItem = newItem selectedItemIndex = 0 } catch { let nsError = error as NSError fatalError("Unresolved error \(nsError), \(nsError.userInfo)") } } } struct DetailView: View { @ObservedObject var item: TestItem var onDelete: () -> Void var body: some View { VStack { Text(item.name) DatePicker("Date", selection: $item.date, displayedComponents: [.date]) HStack { Button(action: { deleteItem(item) }) { Image(systemName: "trash") } Spacer() } Spacer() } } private func deleteItem(_ item: TestItem) { item.managedObjectContext?.delete(item) DispatchQueue.main.async { do { try item.managedObjectContext?.save() onDelete() } catch let error as NSError { print("Error deleting data: \(error.localizedDescription)") } } } }
解决方案
崩溃的核心原因是:删除最后一个实体后,DetailView仍持有已被上下文删除的TestItem实例,@ObservedObject尝试访问已销毁对象的属性时触发异常。可以通过以下方式修复:
1. 优先清空选中状态,再执行删除
修改DetailView的删除逻辑,先调用onDelete清空selectedItem(让DetailView销毁),再删除实体并保存上下文:
private func deleteItem(_ item: TestItem) { onDelete() // 先清空选中状态,销毁DetailView DispatchQueue.main.async { item.managedObjectContext?.delete(item) do { try item.managedObjectContext?.save() } catch let error as NSError { print("Error deleting data: \(error.localizedDescription)") } } }
2. 优化ContentView的onDelete回调
避免强制解包,处理空集合的情况:
DetailView(item: selectedItem, onDelete: { self.selectedItem = nil selectedItemIndex = nil // 如果还有剩余项,选中第一个 if !items.isEmpty { selectedItem = items.first selectedItemIndex = 0 } })
3. 使用@OptionalObservedObject(iOS 16+)
如果支持iOS 16及以上,可以将DetailView的@ObservedObject替换为@OptionalObservedObject,允许实体为nil,避免因对象销毁引发的崩溃:
struct DetailView: View { @OptionalObservedObject var item: TestItem? // 改为可选类型 var onDelete: () -> Void var body: some View { VStack { if let item = item { // 可选绑定安全访问 Text(item.name) DatePicker("Date", selection: $item.date, displayedComponents: [.date]) } HStack { Button(action: { deleteItem() }) { Image(systemName: "trash") } Spacer() } Spacer() } } private func deleteItem() { guard let item = item else { return } onDelete() DispatchQueue.main.async { item.managedObjectContext?.delete(item) do { try item.managedObjectContext?.save() } catch let error as NSError { print("Error deleting data: \(error.localizedDescription)") } } } }
最终效果
调整后,删除最后一个实体时,会先销毁DetailView,再执行删除操作,彻底避免因访问已销毁实体导致的崩溃。
内容的提问来源于stack exchange,提问作者netsplatter
相关产品推荐
相关产品推荐

