12

我有一個問題,我一直在努力幾個星期doozey。它涉及每當我保存Core Data託管對象上下文時出現口吃UI性能。我已經盡我所能,並且正在尋求一些幫助。背景託管對象上下文交錯UI動畫

現狀

我的應用程序使用兩個NSManagedObjectContext實例。一個屬於應用程序委託並具有附加的持久性存儲協調器。另一個是主要MOC的一個孩子,屬於一個Class對象,稱爲PhotoFetcher。它使用NSPrivateQueueConcurrencyType,因此在此MOC上執行的所有操作均發生在後臺隊列中。

我們的應用程序從我們的API下載表示關於照片數據的JSON數據。爲了檢索從我們的API的數據,以下步驟順序進行:

  1. 構造一個NSURLRequest對象,並使用NSURLConnectionDataDelegate協議來構造從請求返回的數據,或處理錯誤。
  2. 一旦JSON數據的下載完成,執行,做以下次級MOC的隊列塊:
    1. 解析使用NSJSONSerialization爲基礎類實例的JSON。
    2. 迭代解析的數據,根據需要在我的後臺上下文中插入或更新實體。通常,這導致大約300個新的或更新的實體。
    3. 保存背景上下文。這將我的更改傳播到主要的MOC。
    4. 在主MOC上執行一個塊以節省它的上下文。這是爲了讓我們的數據保存到磁盤,一個SQLite商店。最後,對委託進行回調,通知他們已將響應完全插入到Core Data存儲中。

的代碼保存背景MOC看起來是這樣的:

[AppDelegate.managedObjectContext performBlock:^{ 
    [AppDelegate saveContext]; //A standard save: call to the main MOC 
}]; 

當主對象上下文保存,它也節省了自上次已經下載了相當數量的JPEG文件發生主對象上下文保存時。目前,在iPhone 4上,我們正在下載70個壓縮率爲70%的15張200x200 JPEG,或總共大約2MB的數據。

問題

這個工作,運作良好。我的問題是,一旦背景上下文保存,運行在我的視圖控制器中的NSFetchedResultsController就會獲取傳播到主MOC的更改。它在我們的PSTCollectionView插入新的單元格,這是一個開源的克隆UICollectionView。在插入新單元格時,主要上下文會保存並將這些更改寫入磁盤。這可以在運行iOS 5.1的iPhone 4上,從250-350毫秒的任何地方。

在三分之一秒內,該應用程序完全沒有響應。在保存之前正在進行的動畫暫停,並且在保存完成之前沒有新的用戶事件發送到主運行循環。

我使用時間探查,以確定是什麼阻止了我們的主線程運行我們的應用程序在儀器。不幸的是,結果相當不透明。這是我從儀器獲得的最重的堆棧軌跡。

Instruments Heaviest Stack Trace

出現要保存更新持久存儲,但我也不能肯定。所以我刪除了任何對saveContext的調用,因此MOC不會觸及磁盤,並且主線程上的阻塞調用仍然存在。

跟蹤,以文本的形式,看起來像這樣:

Symbol Name 
-[NSManagedObjectContext(_NestedContextSupport) _parentObjectsForFetchRequest:inContext:error:] 
-[NSManagedObjectContext executeFetchRequest:error:] 
    -[NSManagedObjectContext(_NestedContextSupport) executeRequest:withContext:error:] 
    _perform 
    _dispatch_barrier_sync_f_invoke 
    _dispatch_client_callout 
     __82-[NSManagedObjectContext(_NestedContextSupport) executeRequest:withContext:error:]_block_invoke_0 
     -[NSManagedObjectContext(_NestedContextSupport) _parentObjectsForFetchRequest:inContext:error:] 
     -[NSManagedObjectContext executeFetchRequest:error:] 
     -[NSPersistentStoreCoordinator executeRequest:withContext:error:] 
      -[NSSQLCore executeRequest:withContext:error:] 
      -[NSSQLCore objectsForFetchRequest:inContext:] 
      -[NSSQLCore newRowsForFetchPlan:] 
      -[NSSQLCore _newRowsForFetchPlan:selectedBy:withArgument:] 
       -[NSSQLiteConnection execute] 

我曾嘗試

之前我們接觸核心數據的代碼,我們做的第一件事就是優化我們的JPEG文件。我們切換到較小的JPEG,並看到性能提升。然後,我們減少了我們一次下載的JPEG數量(從90減少到15)。這也導致了顯着的性能提升。但是,我們仍然在主線程上看到250-350ms長的塊。

我想的第一件事就是剛剛擺脫背景MOC的消除,這可能會導致問題的可能性。事實上,它讓事情變得更糟,因爲我們的更新或創建代碼在主線程上運行,導致整體動畫性能下降。

改變持久店NSInMemoryStoreType沒有效果。

任何人都可以點我的「祕密武器」,這將使我這樣的背景下被管理對象上下文已承諾的UI表現?

+0

看起來你正在保存的對象然後在主線程中出現故障。你能運行核心數據分析器來查看導致錯誤的原因嗎?你有沒有使用反向關係? – ndfred

+0

我會檢查出CD檢查員,但我確實使用inver關係。 –

回答

0

我有一些猜測,在訂單中,你應該檢查他們:

  1. 它看起來極像無論是正在緩慢發生的事情的數據被保存後(即,它是在一個回電話)。這導致我要麼重新加載PTSCollectionView,要麼重新獲取結果控制器。

  2. 此問題是否與UICollectionView在iOS 6.x的發生呢?如果沒有,那會導致我依靠PTSCollectionView

  3. 如果仍然發生,那麼這意味着它可能不是收集視圖,而是取回的結果控制器。它種類的模樣,從堆棧幀(不透明儘管它們可能是)該讀取的結果控制器試圖執行一個獲取其通過dispatch_barrier發生。這些用於確保在達到屏障之後塊纔會被執行。我在這裏一瘸一拐,但你可能想檢查看看,這是因爲在內部,Core Data正在其他地方保存,因此延遲了其他任何請求的執行。再次,這是一種狂野和未受教育的猜測。但我想嘗試沒有取得結果控制器立即更新,看看你的口吃仍然發生。

它代表了我是要對孩子進行MOC了很多工作,但隨後進行保存父另一件事。似乎大部分的保存應該由孩子執行。但它也可能是我沒有在一段時間內與核心數據的這部分工作:-)

6

我會做一些假設,但從你的描述,我認爲這是合理的。

首先,我假設你的主要商務部已經創建了:

[[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType]; 

我做這樣的假設是因爲...

  1. 您可以使用它作爲一個父上下文,所以它必須是或者NSMainQueueConcurrencyTypeNSPrivateQueueConcurrencyType

  2. 既然你在其他地方使用它,你只會這樣做,如果它保證在主隊列上訪問。現在

,我還以爲你已經直接連接主建設部持久存儲協調,而不是另一個父MOC。

當後臺MOC進行保存時,其更改會傳播到主MOC中(儘管它們尚未保存)。由於您的FRC連接到主MOC,它將立即看到這些更改。

當您在主MOC上發出保存時,您的代碼將被阻止,因爲該保存在保存完成之前不會返回。

所以,你看到的是完全預期的。

有很多選擇來解決你的問題。

我的第一個建議是創建一個私有隊列MOC並使其成爲主隊列MOC的父項。

這意味着任何保存的主隊列MOC將而不是塊。相反,它會將數據「保存」到父節點,然後父節點將自己的私有隊列中的實際數據庫保存起來,從而在後臺執行單獨的線程。

現在,這將解決您的主線程阻塞問題。這種機制也很適合加載數據庫的您的子背景MOC。

請注意,iOS 5中存在一些與嵌套上下文相關的錯誤,但如果您的目標是iOS 6,則其中大部分都已修復。

有關更多信息,請參閱Core Data could not fullfil fault for object after obtainPermanantIDs

編輯

你的假設是正確的,但我怕我針對iOS 5的 ,只能有一個父建設部關於iOS 6的主要MOC(它導致 僵局與FRC)。 - Ash Furrow

如果您看到死鎖,請先撥掉您的dispatch_syncperformBlockAndWait來電。除了最簡單的同步(即從數據結構中同步讀取操作)外,在主線程中使用阻塞操作絕不應該發生......然後只有在必要時。此外,同步調用可能會導致意外的死鎖,所以應儘可能避免,特別是在調用任何您不直接控制的代碼時。

如果你不能這樣做,還有其他一些選項。我最喜歡的是將FRC附加到私有隊列MOC,然後通過主線程「粘合」FRC的訪問權限。您可以dispatch_async到主線程來處理表視圖(或其他)的委託更新。例如...

- (void)controllerWillChangeContent:(NSFetchedResultsController *)controller { 
    dispatch_async(dispatch_get_main_queue(), ^{ 
     [tableView beginUpdates]; 
    }); 
} 

您可以創建爲FRC,這僅僅是一個前端,以確保獲得適當同步的代理。它不必是一個完整的代理......就足以滿足您使用FRC的要求......並確保所有操作都適當同步。

它實際上比聽起來容易。

實際上,除了「main-MOC」將是專用隊列MOC而不是主隊列MOC之外,您將擁有與現在完全相同的設置。因此,當數據庫訪問發生時,主線程不會阻塞。

但是,您需要採取一些預防措施以確保您在正確的上下文中使用FRC。

另一種選擇是像現在這樣使用主MOC和FRC來處理對數據庫的更改,但是使數據庫的所有修改都通過單獨的MOC,直接連接到持久存儲協調器。這使更改發生在一個單獨的線程中。然後使用MOC保存通知來更新您的上下文和/或從商店重新獲取數據。

對iOS和OSX的更新修復了嵌入式上下文對於Core Data的很多問題,但在支持以前的版本時,您需要擔心一些問題。

+0

你的假設是正確的,但我擔心我瞄準的是iOS 5並且只能在iOS 6上有一個父MOC到主MOC(它會導致FRC發生死鎖)。 –

+0

這個死鎖是由於[iOS 5的FRC](http://wbyoung.tumblr.com/post/27851725562/core-data-growing-pains)存在問題,而不是由於我的代碼中的任何內容。我會給其他選項一個鏡頭。 –

相關問題