iOS CoreData多Context多線程使用及設計方式

    IOS9開始,創建 NSManagedObjectContext 有兩種方式:NSMainQueueConcurrencyType 和 NSPrivateQueueConcurrencyType:

    NSMainQueueConcurrencyType 的 context 在主線程中運行,用於響應 UI 事件,
    NSPrivateQueueConcurrencyType 的 context 用於處理耗時任務的操作。

    對於耗時的任務,比如保存大量數據時,我們希望使用輔助線程處理,而不在主線程中,防止造成用戶界面卡頓,常見的思路有兩種:

    1. 建立具有上下級關係的多個 context; 注意: parent context中的managedObject發生變化時,會馬上同步給child context中,但child context中的managedObject發生變化時,不會同步給parent,必須調用child context的save!
    2. 建立獨立的並列關係的 context。注意: 並列關係的context中的managedObject變化時,互相獨立,即使save,其他context中的managedObject也不會更變,需要通過通知處理,下文詳細說明。

    後文會介紹兩種方式的具體實現方式,而首先需要注意的是,無論採用那種方式,都需要使用多個 context,不同的 context 可能會在不同的線程來操作,但並不能直接將 context 放到 dispatch queue 或 NSThread 等的 block 中去執行。那應該怎麼用呢?當一個 context 被創建時,它自身會綁定一個隊列,要在不同線程中使用 context 時,不需要我們創建後臺線程然後訪問 context 進行操作,而是交給 context 自身綁定的隊列去處理,我們只需要在下面兩個方法的 Block 中執行操作即可:

     func performBlock(_ block: () -> Void)         //在隊列中異步地執行 Blcok
     func performBlockAndWait(_ block: () -> Void)  //在隊列中同步執行 Block, 會產生阻塞

需要注意的是,你可以在其他線程中調用 context,但是需要調用 context 的上面兩個方法來執行操作,這兩個方法會切換回自己的線程執行操作。

    下面介紹上面提到的兩種思路的具體細節。
    採用多個 context 在多個線程中運作的設計方式主要有以下三種,前兩種方式採用的是思路1,第三種使用的是思路2:

    1. persistentStoreCoordinator <- mainContext <- privateContext(需要IOS5以上)

    這種方式下 mainContext 與 psc 連接,而 privateContext.parent = mainContext,由於 privateContext 沒有直接和 psc 連接,子線程 privateContext 執行 save 操作後,不會進行任何IO操作,而是將變動同步到 mainContext 中,然後在 mainContext 在 mainqueue 中執行 save 時才進行 IO 操作,將數據進行本地持久化,因此可在 privateContext 中進行一些耗時任務,而在 mainContext 中只進行UI相關的操作。
但由於最終數據持久化的 IO 操作必須通過 mainContext 在 main 線程中進行的,因此數據量大時對 UI 線程佔用較大,可能產生阻塞。


    2. persistentStoreCoordinator <- backgroundContext <- mainContext <- privateContext(需要IOS5以上)

    這種方式下backgroundContext與psc連接,mainContext.parent = backgroundContext, privateContext.parent = mainContext,backgroundContext 負責 IO,而前兩步在 privateContext 和 mainContext 中只進行 save,耗時較少,最後在後臺進行數據持久化的任務交給了 backgoundContext。其實我在看到這種結構的時候一直有一個疑惑,就是爲什麼這種結構下還需要 privateContext? background 負責大批量數據變更和 IO,main 負責小量數據同步操作並響應UI不就夠了嗎?後來反應過來,例如在使用 NSFetchedResultsController 時,如果想要在後臺處理過大批量數據後更新界面,只有 back 和 main 的方式就做不到了,因爲 NSFetchedResultsController 綁定的一定是 mainContext,除非在 backContext 操作完後主動發通知,否則 NSFetchedResultsController 不會觸發相關的代理方法。而加入 privateContext,當其 save 時,將變動同步給 mainContxt,就能解決這一問題。

    3.  persistentStoreCoordinator <- mainContext
         persistentStoreCoordinator <- privateContext

    這種方式下,每一個 context 都可以與 psc 進行交互,耗時任務完全放在 privateContext 中進行,涉及 UI 的放在 mainContext 中。但這種方式面臨兩個 context 同步問題,一個 context 的內容變更後,導致另一個 context 中與之有交集的部分不一致,需要手動處理:
    a). 需要添加 NSManagedObjectContextDidSaveNotification 通知,當一個 context 完成 save 後, 需要通知其他 Context 同步,調用 mergeChangesFromContextDidSaveNotification 將數據變動 merge 到當前 context 中。
    b). 提到 merge,必然會想到衝突,對於多個 context 對同一個數據進行了修改,在 save 時會衝突,原因是每一個 context 在 fetch 時會保存當前本地數據的快照,當 save 時會對比這個快照和當前狀態的差異,如果沒有差異則直接合入,如果有差異,說明其他 context 對該塊數據進行過修改,存在衝突,這時,通過 mergePolicy 屬性來進行 merge 的策略選擇,具體 mergePolicy 屬性這裏就不列舉了,可自行查閱文檔。


參考資料:
https://www.jianshu.com/p/283e67ba12a3
https://www.cnblogs.com/breezemist/p/5110387.html
https://www.jianshu.com/p/29d92e3d0e70
https://www.jianshu.com/p/37ab8f336f76
https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/CoreData/Concurrency.html#//apple_ref/doc/uid/TP40001075-CH24-SW1

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章