問題描述
今天遇到一個crash招驴,是子線程操作coredata數(shù)據(jù)的時候發(fā)生的,崩潰棧如下:
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時迭代器出現(xiàn)異常掛了别厘,這種問題一般都是由于容器內(nèi)的數(shù)據(jù)發(fā)生了變化。找到crash的代碼部分拥诡,如下:
NSMutableSet *medias = [localmedia.album.medias mutableCopy];
那么表面上看這句話沒問題啊触趴,沒有別人操作medias啊。那么肯定是多線程造成的了渴肉。找找有沒有其他的線程引用了這個容器冗懦,找了半天,貌似也沒有啊仇祭。
等等披蕉,肯定是錯過了什么!
原來,這部分代碼是有嵌套關(guān)系的没讲,localmedia和album都是coredata數(shù)據(jù)庫中的一個表眯娱,即localmedia是一個視頻,這個視頻屬于一個專輯album爬凑,這個album又包涵了包括這個視頻在內(nèi)的一個視頻列表徙缴。那么在刪除一個視頻的時候,在它對應的專輯中把和這個視頻的關(guān)系也刪除了嘁信。
但是代碼中明明只是在需要的時候用GCD向主線程發(fā)送刪除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的數(shù)據(jù)對象NSManagedObject進行了優(yōu)化,取到的數(shù)據(jù)其實并沒有真正加載到內(nèi)存中既绕。也就是說啄刹,在我們這個問題中,localmedia.album.medias這句話凄贩,表localMedia獲取了表album的數(shù)據(jù)誓军,然后又獲取了album所對應的所有medias,但是這時的medias并沒有真實加載到內(nèi)存中疲扎,所以當我們刪除一個localMedia時昵时,medias事實上是會發(fā)生變化的,這也就是我們在copy的時候crash的原因椒丧。因為主線程在刪除視頻壹甥,子線程還在執(zhí)行遍歷操作,造成了沖突壶熏。同時句柠,這也是為什么調(diào)用棧里打印的是_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];