Coredata操作Fault对象数据造成的crash [_NSFaultingMutableSet mutableCopyWithZone:]

问题描述

今天遇到一个crash,是子线程操作coredata数据的时候发生的,崩溃栈如下:

Last Exception Backtrace:
0   CoreFoundation                  0x22340fea __exceptionPreprocess + 122
1   libobjc.A.dylib                 0x30c84c86 objc_exception_throw + 34
2   CoreFoundation                  0x22340a6c __NSFastEnumerationMutationHandler + 124
3   CoreFoundation                  0x222b77a4 -[NSMutableSet setSet:] + 556
4   CoreData                        0x22083d06 -[_NSFaultingMutableSet mutableCopyWithZone:] + 110
5   XXXX                            0x00286a18 -[DownloadManager removeDownloadLight:success:] (DownloadManager.m:834)
6   XXXX                            0x00287630 __49-[DownloadManager removeMultiDownload:success:]_block_invoke_2 (DownloadManager.m:984)
7   CoreFoundation                  0x2226b2b8 __53-[__NSArrayI enumerateObjectsWithOptions:usingBlock:]_block_invoke + 44
8   CoreFoundation                  0x2226430a -[__NSArrayI enumerateObjectsWithOptions:usingBlock:] + 230
9   XXXXX                           0x002875a0 __49-[DownloadManager removeMultiDownload:success:]_block_invoke (DownloadManager.m:983)
10  libdispatch.dylib               0x312152de _dispatch_call_block_and_release + 6
11  libdispatch.dylib               0x3121f37c _dispatch_root_queue_drain + 1384
12  libdispatch.dylib               0x312203be _dispatch_worker_thread3 + 90
13  libsystem_pthread.dylib         0x3137cdbc _pthread_wqthread + 664
14  libsystem_pthread.dylib         0x3137cb10 start_wqthread + 4

很明显,是进行mutableCopy时迭代器出现异常挂了,这种问题一般都是由于容器内的数据发生了变化。找到crash的代码部分,如下:

NSMutableSet *medias = [localmedia.album.medias mutableCopy];

那么表面上看这句话没问题啊,没有别人操作medias啊。那么肯定是多线程造成的了。找找有没有其他的线程引用了这个容器,找了半天,貌似也没有啊。

等等,肯定是错过了什么!
原来,这部分代码是有嵌套关系的,localmedia和album都是coredata数据库中的一个表,即localmedia是一个视频,这个视频属于一个专辑album,这个album又包涵了包括这个视频在内的一个视频列表。那么在删除一个视频的时候,在它对应的专辑中把和这个视频的关系也删除了。

但是代码中明明只是在需要的时候用GCD向主线程发送删除localmedia的任务,并没有操作medias这个容器啊。

这里面涉及到coredata的一个知识,fault属性。我们看下苹果的解释:

Faulting
Managed objects typically represent data held in a persistent store. In some situations a managed object may be a “fault”—an object whose property values have not yet been loaded from the external data store—see Faulting and Uniquing for more details. When you access persistent property values, the fault “fires” and the data is retrieved from the store automatically. This can be a comparatively expensive process (potentially requiring a round trip to the persistent store), and you may wish to avoid unnecessarily firing a fault.

也就是说,一些情况下,为了提高性能,苹果对coredata的数据对象NSManagedObject进行了优化,取到的数据其实并没有真正加载到内存中。也就是说,在我们这个问题中,localmedia.album.medias这句话,表localMedia获取了表album的数据,然后又获取了album所对应的所有medias,但是这时的medias并没有真实加载到内存中,所以当我们删除一个localMedia时,medias事实上是会发生变化的,这也就是我们在copy的时候crash的原因。因为主线程在删除视频,子线程还在执行遍历操作,造成了冲突。同时,这也是为什么调用栈里打印的是_NSFaultingMutableSet的原因。

解决方案

1 第一个方案比较笨,就是通过一些线程同步的方式解决,比如加锁或者dispatch_sync等方式进行同步的查找遍历,不过这样改动量不少。
2 在stackoverflow上找到一种方式,改动比较简单。
http://stackoverflow.com/questions/6139989/core-data-nsoperation-crash-while-enumerating-through-and-deleting-objects/6140555#6140555

NSSet *iterItems = [NSSet setWithSet:list.items];
    原文作者:jokers200
    原文地址: https://www.jianshu.com/p/51a88c56af68
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞