Xcode报错:Thread 1: EXC_BAD_ACCESS (code=1, address=0x12a2a4000)
Xcode报错:Thread 1: EXC_BAD_ACCESS (code=1, address=0x12a2a4000)

Xcode报错:Thread 1: EXC_BAD_ACCESS (code=1, address=0x12a2a4000)

在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也没有再次报错。因此怀疑是偶发的报错,具体的报错代码还需要进一步排查。

   

如果您认为这篇文章给您带来了帮助,您可以在此通过支付宝或者微信打赏网站开发者。

欢迎加入我们的 微信交流群QQ交流群,交流更多精彩内容!
微信交流群二维码 QQ交流群二维码

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注