5

Ich stieß auf merkwürdiges Verhalten, wenn NSManagedObjectContext 's performBlock: mit Benachrichtigungszentrale verwendet.NSManagedObjectContext: performBlockAndWait vs performBlock mit Benachrichtigungszentrale

Vom Haupt-UI-Thread ich Trigger asynchronen Daten herunterladen (mit NSURLConnectionconnectionWithRequest:). Wenn die Daten der folgenden Delegatmethode ankommen heißt:

- (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]; 
    }]; 
} 

Die savetoCoreData: Methode einfach neue Daten in den Hintergrund Kontext zu speichern:

- (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]; 
} 

Im View-Controller, in viewDidLoad ich hinzufügen Beobachter auf die Benachrichtigung Zentrum:

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

Und dann in den contexChanged: ich den Hintergrund Zusammenhang mit dem Haupt Kontext verschmelzen so tha t sind meine NSFetchedResultsController der Delegatmethoden genannt, wo meine Ansicht aktualisiert werden kann:

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

    [self.managedObjectContext mergeChangesFromContextDidSaveNotification:notification]; 
} 

Es scheint alles gut zu funktionieren, aber es gibt eine Sache, die mich stört. Wenn in downloadCompleted: Methode verwende ich performBlock: anstelle von performBlockAndWait: die Benachrichtigung scheint sich zu verzögern. Es dauert bemerkenswerte (ca. 5s) Zeit von dem Moment an, in dem der Hintergrund-Thread save: bis zu dem Moment NSFetchedResultsController Aufrufe seiner Stellvertretung. Wenn ich performBlockAndWait: verwende, beobachte ich keine sichtbare Verzögerung - der Delegierte wird so schnell aufgerufen, als ob ich saveToCoreData: innerhalb _dispatch_async_ angerufen hätte.

Hat jemand das vorher gesehen und weiß, ob das normal ist oder missbrauche ich etwas?

+4

Beachten Sie, dass die Benachrichtigung für den Hintergrundthread ausgelöst wird und Sie Änderungen an den Hauptkontext in einem Hintergrundthread zusammenführen, was sehr schlecht ratsam ist. Verwenden Sie '[self.managedObjectContext performBlockAndWait: ...]' –

+1

Das ist in der Tat vollkommen sinnvoll. Danke Dan! Ich habe nicht erkannt, dass 'connectionWithRequest:' die Delegate-Methoden für "(...) den Thread aufruft, der den asynchronen Ladevorgang für das verknüpfte NSURLConnection-Objekt gestartet hat." Dies kann leicht durch Überprüfen von '[NSThread isMainThread]' beobachtet werden. –

Antwort

2

Wie Dan in einem der Kommentare darauf hingewiesen hat, sollte die Zusammenführungsoperation auf dem Hauptthread stattfinden. Dies kann leicht durch Änderung der contextChanged: Verfahren beobachtet werden, Folgendes zu tun:

- (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]; 
} 

Mit dieser Änderung sowohl performBlock: und performBlockAndWait: arbeiten.

Solange dies zu einem gewissen Grad erklärt, warum die Probleme in erster Linie aufgetreten sind, verstehe ich immer noch nicht, warum performBlock: und performBlockAndWait: anders als die Threading-Perspektive durchführen. Apple-Dokumentation sagt:

performBlock: und performBlockAndWait: sicherzustellen, dass die Blockoperationen in der Warteschlange für den Kontext angegeben ausgeführt werden. Die Methode performBlock: kehrt sofort zurück und der Kontext führt die Blockmethoden in einem eigenen Thread aus. Mit der Methode performBlockAndWait: führt der Kontext die Blockmethoden immer noch in seinem eigenen Thread aus, aber die Methode kehrt erst zurück, wenn der Block ausgeführt wurde.

Dies zeigt, dass, wenn die wahre Ursache des in der Frage beschriebenen Problems, dass Verschmelzung im Hintergrund-Thread geschieht, dann soll ich unabhängig identisches Verhalten beobachten, welche Methode ich rufe: performBlock: und performBlockAndWait: - Beide werden in einem separaten Thread ausgeführt.

Alsdie NSURLConnection ruft die Delegate-Methode auf den gleichen Thread, der die asynchrone Ladevorgang - Hauptthread in meinem Fall gestartet - Aufruf von Hintergrund Kontext scheint überhaupt nicht erforderlich. NSURLConnections liefern ihre Ereignisse sowieso in der Laufschleife.

+0

Ich glaube, dass der letzte Satz der Apple-Dokumentation falsch ist. –

+0

Ich glaube, dass der letzte Satz richtig ist. Ich habe kürzlich beim Testen mit '-com.apple.CoreData.ConcurrencyDebug 1' auf diese subtile Unterscheidung gestoßen. Diese Antwort erklärt es nett: http://stackoverflow.com/a/19439817/140799 – pohl

Verwandte Themen