SwiftUI+CoreData应用删除列表项后偶现EXC_BAD_ACCESS崩溃及优化问询
我来帮你逐一解决这些问题,结合你的代码给出具体的优化方案:
1. 实现CoreData列表自动刷新的最简方案
你之前手动调用fetchAll()的方式不仅繁琐,还引发了循环调用的崩溃问题。正确的做法是利用CoreData的上下文变化通知,让Model自动监听数据变更并更新列表:
修改Model.swift,移除tasks的didSet代码,改用Combine监听CoreData的通知:
import Foundation import CoreData import Combine class Model: ObservableObject { private let context: NSManagedObjectContext @Published var tasks: [Task] = [] private var cancellables = Set<AnyCancellable>() init(_ viewContext: NSManagedObjectContext) { self.context = viewContext // 监听CoreData上下文变化,自动刷新列表 NotificationCenter.default.publisher(for: NSManagedObjectContext.objectsDidChangeNotification, object: context) .receive(on: DispatchQueue.main) .sink { [weak self] _ in self?.fetchAll() } .store(in: &cancellables) } // 其他原有方法保持不变 }
这样一来,不管是添加、删除还是修改任务,CoreData都会发送通知触发fetchAll(),@Published修饰的tasks会自动通知View刷新,完全不需要手动调用刷新方法。
2. 代码优化与稳定性提升
你的崩溃问题根源在于tasks的didSet中调用fetchAll(),导致无限循环:fetchAll()修改tasks触发didSet,didSet又调用fetchAll(),反复执行最终引发线程错误。我们先解决这个核心问题,再做其他优化:
核心修复:移除循环调用
彻底删除tasks的didSet代码,就是你之前添加的这段:
didSet { DispatchQueue.main.async { self.fetchAll() } }
其他优化点:
简化TaskRow的Toggle绑定
原来的自定义Binding可以直接用@ObservedObject的属性绑定,配合onChange更新时间戳:
// TaskRow.swift Toggle("", isOn: $task.checked) .onChange(of: task.checked) { _ in task.updatedTimestamp = Date() model.save() }
简化addTask方法
用Task(context: context)创建的对象会自动加入上下文,不需要手动调用context.insert(task):
// Model.swift func addTask(_ text: String) { let name = text.trimmingCharacters(in: .whitespacesAndNewlines) guard !name.isEmpty else { return } let task = Task(context: context) task.id = UUID() task.name = name task.creationTimestamp = Date() task.updatedTimestamp = Date() save() }
保护Context不被外部修改
把Model中的context改为private let,避免外部代码意外修改上下文:
// Model.swift private let context: NSManagedObjectContext
简化删除逻辑
onDelete可以直接通过索引获取任务并删除:
// TaskList.swift private func onDelete(with indexSet: IndexSet) { indexSet.forEach { model.context.delete(model.tasks[$0]) } model.save() }
3. 删除操作后的红线问题
这个红线是macOS早期SwiftUI版本的已知bug,在Xcode 11.x对应的SwiftUI中比较常见,属于List组件在删除项后渲染异常导致的残留分割线。
临时缓解方案:给List添加一个动态ID,当任务数量变化时强制List重新渲染:
// TaskList.swift List(selection: $selection){ // 原有代码保持不变 } .id(model.tasks.count) // 新增这行
彻底解决的话,建议升级到兼容macOS 10.15的最新Xcode版本(比如Xcode 12.4),后续版本的SwiftUI已经修复了这个问题。
内容的提问来源于stack exchange,提问作者NikzJon

