你好,歡迎來到IOS教程網

 Ios教程網 >> IOS編程開發 >> IOS開發基礎 >> CoreData 與 ReactiveCocoa 混用時要注意的線程問題

CoreData 與 ReactiveCocoa 混用時要注意的線程問題

編輯:IOS開發基礎

今天在回顧公司項目的一個 Crash 報告時,突然想明白它是怎麼發生的了。這個問題隱藏在復雜設計的背後,不容易發現,但理清楚以後卻又這麼簡單,這讓我直接有了感悟:不要做復雜的設計,同時對底層機制要保持清楚的認識。

讓我簡單的來描述下這個 Crash 的來龍去脈吧。

這個問題,從用戶的角度來表現是這樣的:

一個客戶端程序,用戶打開它以後,它自動刷新內容,在刷新的過程中,用戶輕觸了某個條目點進去看細節。這個時候,非常低的幾率會 crash。

而這個動作的背後後,主要是有 ReactiveCocoa 和 CoreData 在做事情:

刷新內容的時候,CoreData 會在自己的 Queue 裡面對從網絡上獲取到的數據進行本地持久化,完成後會更新 UI;而用戶點擊某個具體條目時,也是在 CoreData 的 Queue 裡面判斷條目的評論是不是都獲取了,如果沒有,則進行網絡獲取,不同的是,在這裡獲取後會進行內容渲染(在 ReactiveCocoa 的代碼裡,需要回到主線程),獲取和渲染都完成後,發送 RACSignal 信號並進行 UI 的更新。

剛接手這個項目接觸這段代碼時,我也覺得有點復雜,不過它一直沒出過什麼問題,也就不在意了。

後來當我在項目裡集成了 Crash 匯報機制後,偶爾收到的 Crash 報告讓我引起了注意,這個 Crash 報告是,來自 CoreData 在存儲對象時先查詢這個對象之前是否存在的代碼:

NSGenericException*** Collection<__nscfset: 0x1742769c0="">was mutated while being enumerated.

這個 Crash 的意思是,在查詢對象時,查詢對象所在的區域正在被修改。但是我幾次確認了代碼,查詢和存儲 CoreData 對象的代碼都是在 performBlock 塊裡面的,只有渲染內容時才會回到主線程,理應不會發生這樣的問題。到底是怎麼了?

今天我就突然頓悟了,根據下圖作解釋:

這裡的線程情況有點迷惑人,直觀看代碼還以為只有 commentRenderSeq 和 entryRenderSeq 是 ReactiveCocoa 的 Signal,會跑在自己的線程裡。實際上,還有圖片箭頭所指的 retrieveComments 也是一個 Signal,它通過 flattenMap 功能會創建渲染時的 Signal,它們的全部完成 commentRenderSeq 才算完成。

於是乎,在 CoreData 的 Private Queue 裡面執行這個 retrieveComments 的時候,這個代碼塊實際執行塊已經脫離當前 CoreData 的線程,躍遷至 ReactivieCocoa 自己維護的線程裡面去干活了。這個時候,如果恰好有另外一個 CoreData 線程在修改數據,而這個 ReactiveCocoa 也在動相關數據,於是上述的 crash 問題造成了。

ReactiveCocoa CoreData

問題在我去看 Crash 報告時的所有線程情況時得到了驗證(啊,如果我一早就養成檢查所有線程情況的習慣,這個問題就能更快解決了)

這個 #8 是 Crash 的線程,如圖所示,這個線程來自 ReactiveCocoa,在 CoreData 的 NSManagedObjectContext 查詢時發生異常

reactivecocoa-and-coredata-01

與此同時,#4 線程,也就是通過 CoreData 的 performBlock 裡自己維護的一條獨立線程,在這個時候它也在 perform 數據,於是就產生了前面所述的兩條線程同時修改數據造成的問題了。

reactivecocoa-and-coredata-03

怎麼解決這個問題?重構這部分代碼,放棄這個復雜的設計,用更簡單直白的即可。

通過這個 Bug,我感悟到現在很多問題都是由多線程引起的,而我在 Debug 的時候卻只看當前線程,沒有養成查看所有線程的習慣,導致這個問題困擾我很久。幸好對 ReactiveCocoa 和 CoreData 的基本特性還算了解,知道它們都是會維護自己的線程並在裡面做事,不然還真找不到這個問題。

最後,不要做復雜設計的,線程不是量子,不要躍遷三次以上!不然還真找不到 Bug :D

(本文作者:圖拉鼎)

  1. 上一頁:
  2. 下一頁:
蘋果刷機越獄教程| IOS教程問題解答| IOS技巧綜合| IOS7技巧| IOS8教程
Copyright © Ios教程網 All Rights Reserved