EXC_BAD_ACCESS 关于 mergeChangesFromContextDidSaveNotification

EXC_BAD_ACCESS on mergeChangesFromContextDidSaveNotification

我们一直在尝试调试 Core Data multiple-context/threading 问题,其中将 Core Data 保存通知合并到我们的主线程 NSManagedObjectContext 偶尔会使应用程序崩溃。这导致大约 2% 的应用程序会话崩溃,我们不知道如何解决这个问题。如果您能就可能导致此崩溃的原因提供任何指导或一般性建议,我们将不胜感激。

我们有一个如下所示的核心数据设置:

N.B。这是从 [MagicalRecord setupAutoMigratingCoreDataStack]

创建的 Magical Record v2.3 中的默认核心数据堆栈

这是我们的应用程序崩溃的场景:

  1. HTTP 请求returnsJSON
  2. JSON 在 Root Saving Context
  3. 上被解析为 NSManagedObjects(一些新实体,一些更新的实体)
  4. 根保存上下文 保存到持久存储
  5. NSManagedObjectContextDidSaveNotification 由 Core Data 广播。主队列上的默认上下文观察到这一点并调用 mergeChangesFromContextDidSaveNotification: 主线程上的 NSDictionary 更改。
  6. objectID 被发送到无效对象时崩溃(很可能 NSManagedObject 已被释放)。

这是在 NSManagedObjectContext mergeChangesFromContextDidSaveNotification: 的私有实现中发生的,所以我们不可能看到这里到底出了什么问题;在这一点上我们只能说一个应该存在的对象不存在。

这只发生在一小部分 Core Data 保存中,表明这可能不是我们 Core Data → API 堆栈中的根本缺陷。此外,没有迹象表明上下文变化中的变化大小或类型(insertions/updates/deletions)对崩溃的可能性有任何影响。

NSManagedObjectContextDidSaveNotification 的文档说:

"You can pass the notification object to mergeChangesFromContextDidSaveNotification: on another thread, however you must not use the managed object in the user info dictionary directly on another thread. For more details, see Concurrency with Core Data in Core Data Programming Guide."

也许这就是问题所在?我会确保你从通知中获得的对象被保存在默认上下文中,在它由 Root 发布的同一线程上。

一种可能是您的持久存储已损坏并且处于不一致状态。如果发生这种情况,则会生成错误代码,而 Magical Record 不一定会处理该错误代码。这可能是许多与 Magical Record 相关的难以重复的明显随机崩溃的来源(并且可能会或可能不会被视为 Magical Record 错误)。

值得一读 Magical Record 问题线程 here (same issue) and here(不同的问题,但可能是类似的原因)。当我遇到这些问题时,我设法按照这些线程中的各种提示进行了一些临时补丁修复,但最终我决定删除我对 Magical Record 的依赖,从那以后我再也没有遇到过问题。

自从发布这个问题以来已经有一段时间了,在重新发现它之后,为了找到这个话题的其他人,我想回答我自己的问题。

在我的情况下,我已经从同级 NSManagedObjectContexts 迁移了一个大型代码库,并通过 NSManagedObjectContextDidSaveNotification 进行了更新。然而,问题实际上与此无关,即使这确实暴露了问题。

真正的原因是代码的较旧部分,由以前的工程师设置,在 NSManagedObjects 及其属性上设置了 KVO。事实证明,核心数据实体上的 KVO 实际上是一个非常非常糟糕的主意。

更准确地说,这似乎是在实体上设置 KVO 并且从 NSPersistentStore 中删除对象或此对象上的关系目标时发生的。第二个条件似乎不是问题的唯一原因,但在我的情况下绝对是一个非常突出的原因。

经验教训:

  1. 需要时使用抓取结果控制器。 KVO 不是一个方便的捷径,你不应该避免将狡猾的核心数据 KVO 代码迁移到 NSFetchedResultsControllers 或其他明智的选择,因为拖延只会伤害你。
  2. 多线程 Core Data 是一项很难但非常值得成为专家的技能。了解您的 Core Data 堆栈以及 Core Data 多线程的细微差别和局限性绝对值得所有的精神痛苦。