5

我遇到了有趣的行爲,當使用NSManagedObjectContextperformBlock:與通知中心。NSManagedObjectContext:performBlockAndWait與performBlock與通知中心

從主UI線程我觸發異步數據下載(使用NSURLConnectionconnectionWithRequest:)。當數據到達下面的委託方法被調用:

- (void)downloadCompleted:(NSData *)data 
{ 
    NSArray *new_data = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil]; 

    self.backgroundObjectContext = [[NSManagedObjectContext alloc] 
          initWithConcurrencyType:NSPrivateQueueConcurrencyType]; 
    self.backgroundObjectContext.persistentStoreCoordinator = self.persistentStoreCoordinator; 

    [self.backgroundObjectContext performBlockAndWait:^{ 
     [self saveToCoreData:new_data]; 
    }]; 
} 

savetoCoreData:方法簡單地保存新的數據到後臺背景:

- (void)saveToCoreData:(NSArray*)questionsArray 
{ 
    for (NSDictionary *questionDictionaryObject in questionsArray) { 
     Question *newQuestion = [NSEntityDescription 
         insertNewObjectForEntityForName:@"Question" 
           inManagedObjectContext:self.backgroundObjectContext]; 

     newQuestion.content = [questionDictionaryObject objectForKey:@"content"];    
    } 

    NSError *savingError = nil; 
    [self.backgroundObjectContext save:&savingError]; 
} 

在視圖控制器,在viewDidLoad我添加觀察員通知中心:

[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(contextChanged:) 
              name:NSManagedObjectContextDidSaveNotification 
              object:nil]; 

然後在contexChanged:我合併背景上下文與主要上下文,所以臨時T分別叫我NSFetchedResultsController的委託方法在我的視圖可以更新:

- (void)contextChanged:(NSNotification*)notification 
{ 
    if ([notification object] == self.managedObjectContext) return; 

    [self.managedObjectContext mergeChangesFromContextDidSaveNotification:notification]; 
} 

這一切似乎運作良好,但有一件事困擾我。當我在downloadCompleted:方法中使用performBlock:而不是performBlockAndWait:時,通知似乎被延遲。從後臺線程執行​​直到NSFetchedResultsController調用其委託時,需要大量時間(大約5秒)。當我使用performBlockAndWait:時,我沒有觀察到任何可見的延遲 - 代表被呼叫的速度如同我在_dispatch_async_內呼叫saveToCoreData:一樣快。

有沒有人看到過,並知道這是正常的還是我濫用的東西?

+4

請注意,通知是在後臺線程中引發的,並且您將後臺線程中的主要上下文合併到非常不適宜的主環境中。使用'[self.managedObjectContext performBlockAndWait:...]' –

+1

這確實很有道理。謝謝丹!我沒有意識到''connectionWithRequest:'調用了委託方法「(...)啓動相關NSURLConnection對象的異步加載操作的線程。通過檢查'[NSThread isMainThread]'可以很容易地觀察到這一點。 –

回答

2

正如Dan在其中一條評論中指出的,合併操作應該發生在主線程中。這可以通過改變contextChanged:方法做到以下幾點容易觀察:

- (void)contextChanged:(NSNotification*)notification 
{ 
    if ([notification object] == self.managedObjectContext) return; 

    if (![NSThread isMainThread]) { 
     [self performSelectorOnMainThread:@selector(contextChanged:) 
           withObject:notification 
          waitUntilDone:YES]; 
     return; 
    } 

    [self.managedObjectContext mergeChangesFromContextDidSaveNotification:notification]; 
} 

隨着這一變化,無論是performBlock:performBlockAndWait:都在工作。

只要這在一定程度上解釋了爲什麼問題首先發生,我仍然不明白爲什麼performBlock:performBlockAndWait:表現不同於線程的角度。蘋果的文件說:

performBlock:performBlockAndWait:確保塊操作對上下文指定的隊列執行。 performBlock:方法立即返回並且上下文在其自己的線程上執行塊方法。通過performBlockAndWait:方法,上下文仍然在其自己的線程上執行塊方法,但該方法直到執行該塊纔會返回。

這表明,如果在提到的問題的真正根源是合併在後臺線程發生的事情,那麼我應該無論哪一種方法,我呼籲的觀察相同的行爲:performBlock:performBlockAndWait: - 兩者都在sperate線程中執行。

另外,由於NSURLConnection在啓動異步加載操作的同一個線程上調用委託方法 - 在我的情況下是主線程 - 使用後臺上下文似乎根本沒有必要。無論如何,NSURLConnections在運行循環中傳遞它的事件。

+0

我相信蘋果文檔的最後一句話是不正確的。 –

+0

我相信最後一句話是正確的。我最近碰到了這個微妙的區別,同時用'-com.apple.CoreData.ConcurrencyDebug 1'進行測試。這個答案很好地解釋了它: http://stackoverflow.com/a/19439817/140799 – pohl