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

如何避免删除CoreData最后一个实体时SwiftUI DetailView崩溃

CoreData实体删除导致SwiftUI应用崩溃问题

我有一个名为TestItem的CoreData实体:
TestItem实体

当集合中只剩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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.22 22:57:09