NSManagedObjectContext -save:导致 SIGSEGV 崩溃

hgw*_*tle 2 core-data objective-c nsmanagedobjectcontext ios

我看到我的应用程序有几个具有相同核心数据堆栈跟踪的崩溃报告:

在此输入图像描述

我无法重现此崩溃,但我认为这与级联删除关系有关NSManagedObject_propagatePendingDeletesAtEndOfEvent:这是通过堆栈跟踪中看到的调用来判断的。我NSManagedObjectContext的设置如下:

- (NSManagedObjectContext*)managedObjectContext {
    if (_managedObjectContext == nil) {
        _managedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
        [_managedObjectContext setPersistentStoreCoordinator:self.persistentStoreCoordinator];
    }
    return _managedObjectContext;
}
Run Code Online (Sandbox Code Playgroud)

关于什么可能导致-save:整个应用程序中的堆栈跟踪调用崩溃的任何想法?

Mic*_*ael 5

从堆栈跟踪中我只能看到它是一个竞争条件,更具体地说是属性strong或变量(可能在字典中)的数据竞争。由于崩溃发生在主线程上,我的第一个猜测是您在后台线程上使用了不应在后台线程上访问的 API。

  • 为什么我认为这是一场数据竞赛?
  • 由于竞争条件,核心数据代码在主线程上崩溃的最可能原因是什么?
  • 修复此错误的一些建议。

为什么我认为这是一场数据竞赛?

因为崩溃发生在objc_retain. 这只是经验。在我看到因保留对象而崩溃的情况中,十分之九是因为数据争用。在另一种情况下,罪魁祸首是手动内存管理做得错误。如果您对更多细节感兴趣,可以查找 objc_storeStrong() 的来源。

其次,核心数据参考中的并发部分有以下有趣的信息:

NSMainQueueConcurrencyType 专门用于您的应用程序接口,并且只能在应用程序的主队列上使用。

NSPrivateQueueConcurrencyType 配置在初始化时创建自己的队列,并且只能在该队列上使用。因为队列是私有的并且是 NSManagedObjectContext 实例内部的,所以只能通过 PerformBlock: 和 PerformBlockAndWait: 方法来访问它。

由于竞争条件,核心数据代码在主线程上崩溃的最可能原因是什么?

根据您问题中的代码,您正在使用NSMainQueueConcurrencyType,因此核心数据不应在具有此“托管上下文”的后台队列上使用。

我的猜测是您正在从某个后台线程调用核心数据 API。

修复此错误的一些建议。

竞争条件并不总是导致崩溃。因此,有时无法轻易重现此类崩溃。然而,一切并没有失去。

要了解这是否确实是数据竞争,您必须查看完整的崩溃报告。通过崩溃报告,您不仅可以获得崩溃主线程的回溯,还可以获得崩溃发生时进程的所有其他线程的回溯。(只需在崩溃报告中搜索“CoreData”即可。)如果您非常不幸,则在任何后台线程上都看不到核心数据 API。不过,在这种情况下,您应该看到至少一个线程具有一些“autorelasepoolpop”框架。如果您在后台堆栈跟踪中发现了一些“CoreData”帧,请在该堆栈跟踪中查找指向您的应用程序代码的帧。罪魁祸首就在这里。

assert([NSThread isMainThread]);为了进行调试,您可以在各处调用一些调用,无论您在何处调用核心数据 API。如果由于断言失败而崩溃,您就知道问题出在哪里。

如果您使用的是 Xcode 9,您可能想尝试新的“Main Thread Sanitizer”(在“Diagnostics”下的方案设置中,在还配置了“Thread Sanitizer”的同一面板中。您可能想“暂停”问题”)。如果这没有帮助,还可以尝试“Thread Sanitizer”。

您还可以将并发类型切换为NSPrivateQueueConcurrencyType。请务必使用performBlock:performBlockAndWait:调用包装所有核心数据 API 调用,否则您不会得到更少的崩溃,而是更多的崩溃。如果您确实需要使用后台队列中的核心数据(例如出于性能原因),则可以采用这种方法。

如果你幸运的话,这只是某个地方的一个微小的代码错误,并且可以在 10 分钟内修复:-)。如果您非常不幸,应用程序的并发架构已损坏,您必须(重新)设计它:-/。

希望这可以帮助。