执行删除操作时App崩溃:Thread 1: EXC_BREAKPOINT 问题求助
问题描述
我重构了Core Data数据模型,Task实体作为NSManagedObject,包含id(UUID)、name(String)、complete(Bool)、priorityNum(Int32)、date(Date)、para(String)属性。删除操作在MemoryPoolView中执行,虽然删除成功,但App会崩溃,断点停在TaskListRow里调用task.date的位置,需要解决崩溃问题让App正常运行。
Task CoreData 代码
import SwiftUI import CoreData // Create Core Data class for Task entity @objc(Task) public class Task: NSManagedObject { @NSManaged public var id: UUID @NSManaged public var name: String @NSManaged public var complete: Bool @NSManaged public var priorityNum: Int32 @NSManaged public var date: Date @NSManaged public var para: String } extension Task: Identifiable { var priority: Priority { get { Priority(rawValue: Int(priorityNum)) ?? .normal } set { self.priorityNum = Int32(newValue.rawValue) } } } enum Priority: Int { case low = 0 case normal = 1 case high = 2 var priorityType: String { switch rawValue { case Priority.low.rawValue: return "low" case Priority.normal.rawValue: return "normal" case Priority.high.rawValue: return "high" default: return "" } } // to set color for each type of priorities func priorityColor() -> Color { switch rawValue { case Priority.low.rawValue: return .green case Priority.normal.rawValue: return .orange case Priority.high.rawValue: return .red default: return .orange } } }
MemoryPoolView 代码
import SwiftUI struct MemoryPoolView: View { @Environment(\.managedObjectContext) private var viewContext @FetchRequest( entity: Task.entity(), sortDescriptors: [ NSSortDescriptor(keyPath: \Task.complete, ascending: true), NSSortDescriptor(keyPath: \Task.priorityNum, ascending: false) ], animation: .default ) private var tasks: FetchedResults<Task> @State private var showNewTask: Bool = false @State private var searchText: String = "" @State private var selectedTask: Task? var body: some View { NavigationView { ZStack { List { ForEach(tasks.filter { searchText.isEmpty ? true : $0.name.contains(searchText)}) { task in TaskListRow(task: task) } .onDelete(perform: deleteTask) } .listStyle(.sidebar) if tasks.count == 0 { Image("no-data") .resizable() .scaledToFit() } } .navigationTitle("Memories") .toolbar { ToolbarItem(placement: .navigationBarTrailing) { Button(action: { self.showNewTask = true }) { Image(systemName: "plus.circle.fill") .foregroundColor(.accentColor) } } ToolbarItem(placement: .navigationBarTrailing) { EditButton() .foregroundColor(.blue) .opacity(self.tasks.count == 0 ? 0.5 : 1) .disabled(self.tasks.count == 0) } } } .searchable(text: self.$searchText, placement: .automatic) .sheet(isPresented: $showNewTask) { NewTaskView(isShow: self.$showNewTask) .offset(y:30) } } private func deleteTask(index: IndexSet) -> Void { withAnimation { index.map { tasks[$0] }.forEach(viewContext.delete) do { try viewContext.save() } catch { let nsError = error as NSError fatalError("Unresolved error \(nsError.localizedDescription), \(nsError.userInfo)") } } }} #Preview { MemoryPoolView() }
TaskListRow 代码
import SwiftUI struct TaskListRow: View { @ObservedObject var task: Task @Environment(\.managedObjectContext) private var viewContext // to save the updates let dateFormatter: DateFormatter = { let formatter = DateFormatter() formatter.dateStyle = .medium return formatter }() var body: some View { VStack(alignment: .leading, spacing: 8) { Text(task.name) .font(.system(size: 20)) .fontWeight(.bold) .strikethrough(task.complete, color: task.priority.priorityColor()) .opacity(task.complete ? 0.5 : 1) Text(task.para) .font(.body) .foregroundColor(.brown) HStack { Spacer() Text(dateFormatter.string(from: task.date))//the thread breakpoint appears here .font(.caption) .foregroundColor(.secondary) } } .padding(.vertical, 12) .padding(.horizontal, 16) .background(Color.white) .cornerRadius(8) .onReceive(self.task.objectWillChange) { _ in if self.viewContext.hasChanges { try? self.viewContext.save() } } } } struct TaskListRow_Previews: PreviewProvider { static var previews: some View { let testTask = Task(context: PersistenceController.preview.container.viewContext) testTask.id = UUID() testTask.name = "Test Task" testTask.complete = false testTask.priority = .normal testTask.para = "Before you can begin to determine what the composition of a particular paragraph will be, you must first decide on an argument and a working thesis statement for your paper. What is the most important idea that you are trying to convey to your reader? The information in each paragraph must be related to that idea. In other words, your paragraphs should remind your reader that there is a recurrent relationship between your thesis and the information in each paragraph. A working thesis functions like a seed from which your paper, and your ideas, will grow. The whole process is an organic one—a natural progression from a seed to a full-blown paper where there are direct, familial relationships between all of the ideas in the paper." testTask.date = Date() return TaskListRow(task: testTask) .previewLayout(.fixed(width: 350, height: 200)) .padding() } }
崩溃原因与解决方案
崩溃原因
直接对FetchedResults做filter后传递给ForEach,会导致过滤后的数组持有已删除Task对象的引用。Core Data删除对象后,FetchedResults会更新,但过滤数组的同步存在延迟,此时视图仍会尝试访问已被标记为删除的Task属性,触发崩溃。
具体修复步骤
1. 修改MemoryPoolView:用动态谓词处理搜索
将搜索过滤逻辑整合到FetchRequest的谓词中,确保FetchedResults始终返回有效对象,自动同步Core Data状态:
struct MemoryPoolView: View { @Environment(\.managedObjectContext) private var viewContext @State private var showNewTask: Bool = false @State private var searchText: String = "" @State private var selectedTask: Task? // 动态FetchRequest private var fetchRequest: FetchRequest<Task> private var tasks: FetchedResults<Task> { fetchRequest.wrappedValue } init() { fetchRequest = FetchRequest( entity: Task.entity(), sortDescriptors: [ NSSortDescriptor(keyPath: \Task.complete, ascending: true), NSSortDescriptor(keyPath: \Task.priorityNum, ascending: false) ], predicate: NSPredicate(value: true), animation: .default ) } var body: some View { NavigationView { ZStack { List { ForEach(tasks) { task in TaskListRow(task: task) } .onDelete(perform: deleteTask) } .listStyle(.sidebar) // 监听搜索文本变化,更新谓词 .onChange(of: searchText) { newValue in if newValue.isEmpty { fetchRequest.predicate = NSPredicate(value: true) } else { fetchRequest.predicate = NSPredicate(format: "name CONTAINS[c] %@", newValue) } } if tasks.count == 0 { Image("no-data") .resizable() .scaledToFit() } } .navigationTitle("Memories") .toolbar { ToolbarItem(placement: .navigationBarTrailing) { Button(action: { showNewTask = true }) { Image(systemName: "plus.circle.fill") .foregroundColor(.accentColor) } } ToolbarItem(placement: .navigationBarTrailing) { EditButton() .foregroundColor(.blue) .opacity(tasks.count == 0 ? 0.5 : 1) .disabled(tasks.count == 0) } } } .searchable(text: $searchText, placement: .automatic) .sheet(isPresented: $showNewTask) { NewTaskView(isShow: $showNewTask) .offset(y:30) } } private func deleteTask(index: IndexSet) { withAnimation { index.map { tasks[$0] }.forEach(viewContext.delete) do { try viewContext.save() } catch { let nsError = error as NSError fatalError("Unresolved error \(nsError.localizedDescription), \(nsError.userInfo)") } } } }
2. 修改TaskListRow:移除冗余逻辑+防御性访问
删除不必要的自动保存逻辑,对Task属性做可选绑定,避免访问已删除对象的属性:
struct TaskListRow: View { @ObservedObject var task: Task let dateFormatter: DateFormatter = { let formatter = DateFormatter() formatter.dateStyle = .medium return formatter }() var body: some View { VStack(alignment: .leading, spacing: 8) { Text(task.name) .font(.system(size: 20)) .fontWeight(.bold) .strikethrough(task.complete, color: task.priority.priorityColor()) .opacity(task.complete ? 0.5 : 1) Text(task.para) .font(.body) .foregroundColor(.brown) HStack { Spacer() // 防御性访问,避免已删除对象引发崩溃 if let date = task.date as Date? { Text(dateFormatter.string(from: date)) .font(.caption) .foregroundColor(.secondary) } } } .padding(.vertical, 12) .padding(.horizontal, 16) .background(Color.white) .cornerRadius(8) } }
修复说明
- 用
FetchRequest动态谓词替代手动过滤,确保数据源始终与Core Data状态同步,删除操作后对象会自动从列表中移除。 - 移除
TaskListRow中的自动保存逻辑,避免上下文状态混乱,删除操作的保存统一在MemoryPoolView中处理。 - 对
task.date做可选绑定,作为最后一层防御,避免极端情况下的崩溃。
内容的提问来源于stack exchange,提问作者Aahlaad Ishaan
相关产品推荐
相关产品推荐

