在Xcode运行的过程中,提示:
Thread 1: EXC_BAD_ACCESS (code=1, address=0x12a2a4000)
经过查询了解到Thread 1: EXC_BAD_ACCESS (code=1, address=0x…) 是 iOS/macOS 开发中常见的内存访问错误,表示程序试图访问一个已经被释放或未分配的内存地址。这通常意味着访问了一个悬空指针(dangling pointer)或者一个已经被 Core Data 或系统管理机制回收的对象。
触发场景
该报错场景于Core Data或Swift中。
在调试Core Data清理历史数据时,触发了这一报错:
// 清理 Core Data数据库中的历史 Gold 图表数据
let deleteFetchRequest: NSFetchRequest<NSFetchRequestResult> = YahooGoldPricePoint.fetchRequest()
let deleteRequest = NSBatchDeleteRequest(fetchRequest: deleteFetchRequest)
// 从上下文中删除全部的 Gold 图表数据
try context.execute(deleteRequest)
context.reset() // 重置 context,释放内存中已删除的对象
因此,怀疑是context.reset()导致的这一问题。
当使用context.reset()释放删除的数据后,再去访问原本存在于context中的对象(比如YahooGoldPrice),会引发EXC_BAD_ACCESS。
因为 reset() 会使所有托管对象失效,原先引用的对象会变成“悬空引用”,再访问它就会触发此类错误。
例如:
let price = yahooGoldPrice // 是 context 中的托管对象
context.reset()
// 再访问 price.someProperty 会 EXC_BAD_ACCESS!
解决方案
1、不要在重置 context 之后访问旧对象
context.reset() 会让之前的 NSManagedObject 都变成无效引用。
如果必须 reset(),那就:
1)先保存需要的数据到临时变量(比如结构体或 Dictionary)
2)再 reset
3)然后重新 fetch 或 rebuild 托管对象。
2、更推荐:使用 context.refreshAllObjects() 替代 reset
它会刷新对象,但不会使它们失效或释放引用。
排查原因
如果需要排查具体的哪一行代码导致的报错:
可以开启Zombie Objects 检查(建议开发调试时开启)
1、打开 Xcode 的 Scheme 设置(Product > Scheme > Edit Scheme)。

2、在 “Run” > “Diagnostics” 中勾选 “Zombie Objects”。

3、再运行程序,Xcode 会明确告诉你是哪个类的对象被提前释放了。
总结
EXC_BAD_ACCESS报错的原因为访问了已经释放的内存(悬空 Core Data 对象等)。为了避免 reset 后再访问旧对象;可以改用 refreshAllObjects() 更安全。
try context.execute(deleteRequest)
context.refreshAllObjects() // 更安全,不会影响已修改未保存的对象
最后,当我把context.reset()放到方法的最前面,而更新Core Data的方法放到最后面。
func fetchahooGoldPrice() {
let context = CoreDataPersistenceController.shared.context
/// 清理代码放在最前面
// 清理 Core Data数据库中的历史 Gold 图表数据
let deleteFetchRequest: NSFetchRequest<NSFetchRequestResult> = YahooGoldPricePoint.fetchRequest()
let deleteRequest = NSBatchDeleteRequest(fetchRequest: deleteFetchRequest)
// 从上下文中删除全部的 Gold 图表数据
try context.execute(deleteRequest)
context.reset() // 重置 context,释放内存中已删除的对象
/// 获取和更新 Core Data 的代码放在后面
// 查询 Core Data 中 Yahoo 黄金的数据条件
let fetchRequest: NSFetchRequest<YahooGoldPrice> = YahooGoldPrice.fetchRequest()
// 获取 Core Data 中 Yahoo 黄金的数据
let results = try context.fetch(fetchRequest)
...
}
这样就是先清理数据并调用context.reset(),而不会影响到获取的更新方法。
重新运行App,不再报错。
但是,当写这篇文章的时候,尝试将这一问题复现,发现context.reset()恢复到原来的位置,运行App也没有再次报错。因此怀疑是偶发的报错,具体的报错代码还需要进一步排查。