2014-01-29 10 views
5

Ich schreibe gerade die Sync-Engine meiner iOS-App. Eine der Methoden, die ich schreibe, ist eine Reload-Datenfunktion, bei der die App die Daten des Nutzers sowie alle seine Fotos erneut herunterlädt. Dies ist eine teure Operation (zeitweise), so dass ich eine NSOperation Unterklasse, SSReloadDataOperation, erstellt habe. Er lädt die Daten herunter, ruft die Entität currentUser ab, entfernt alle vorhandenen Fotos aus diesem currentUser und füllt sie erneut auf.Kerndaten Thread- und Sperrkonfliktprobleme

Obwohl ich dachte, das war thread-safe, manchmal während der Betrieb läuft und -currentUser von woanders zugegriffen wird, stürzt die App, vermutlich beim Versuch, es zu holen. Andere Zeiten, die Benutzeroberfläche manchmal nur friert, und die Pause im Debugger zeigt es immer an einem -currentUserNSFetchRequest Ausführungsaufruf.

Wie mache ich diese Operation Thread-sicher und atomar, so dass ich herunterladen kann und neu zu besiedeln, ohne das Haupt UI-Thread zu blockieren, und -currentUser noch zugänglich sein? Gibt es etwas, das mir in Bezug auf die Verwendung von Schlössern oder Architektur fehlt? Vielen Dank!

Code:

- (void)main 
{ 
    // Download the photo data 
    [[SyncEngine defaultEngine] getUserPhotosWithCompletionBlock:^(NSMutableArray *photos) 
    { 
    if (photos) 
    { 
     // Create a new NSManagedObjectContext for this operation 

     SSAppDelegate* appDelegate = [[UIApplication sharedApplication] delegate]; 
     NSManagedObjectContext* localContext = [[NSManagedObjectContext alloc] init]; 
     [localContext setPersistentStoreCoordinator:[[appDelegate managedObjectContext] persistentStoreCoordinator]]; 

     NSNotificationCenter* notificationCenter = [NSNotificationCenter defaultCenter]; 
     [notificationCenter addObserver:self 
           selector:@selector(mergeChanges:) 
            name:NSManagedObjectContextDidSaveNotification 
            object:localContext]; 

     NSError* error; 
     NSFetchRequest* request = [[[SyncEngine defaultEngine] managedObjectModel] fetchRequestFromTemplateWithName:@"CurrentUser" substitutionVariables:[[NSDictionary alloc] init]]; 
     User* currentUser = [[localContext executeFetchRequest:request error:&error] objectAtIndex:0]; 

     // Remove the old data 
     [currentUser setPhotos:[[NSSet alloc] init]]; 

     // Iterate through photo data, repopulate 
     for (Photo* photo in photos) { 
      [currentUser addPhotosObject:photo]; 
     } 

     if (! [localContext save:&error]) { 
      NSLog(@"Error saving: %@", error); 
     } 

     NSLog(@"Completed sync!"); 
    } 
    } userId:[[[SyncEngine defaultEngine] currentUser] userId]]; 
} 

-currentUser bequeme Methode, in der Regel aus dem Haupt-Thread genannt.

- (User *)currentUser 
{ 
    NSError* error; 
    NSFetchRequest* request = [self.managedObjectModel fetchRequestFromTemplateWithName:@"CurrentUser" substitutionVariables:[[NSDictionary alloc] init]]; 
    NSArray* result = [self.managedObjectContext executeFetchRequest:request error:&error]; 
    if ([result count] == 1) { 
    return [result objectAtIndex:0]; 
    } 
    return nil; 
} 
+1

Martin R muss auf diesem einspielen. – sangony

+0

Was meinen Sie mit "normalerweise aus dem Hauptthread" (nicht immer)? Außerdem entfernen Sie den Beobachter nicht in Ihrem Abschlussblock. Bitte teilen Sie die Absturzmeldung mit. Schließlich wäre es schön, sich die 'mergeChanges:' Methode anzuschauen. –

Antwort

1

Sie haben Recht, dieses Einfrieren fühlt sich an wie ein Threading-Problem.

Da verwaltete Objekte in der gleichen Thread- oder seriellen Warteschlange verwendet werden müssen, in der ihr Kontext verwendet wird und wo sie erstellt wurden, ist es unmöglich, eine Methode wie aus dem Beispiel zu verwenden und sie thread-safe-verwaltet magisch zurückzugeben Objekt.

Aber Sie könnten Kontext als Parameter übergeben. Der Anrufer entscheidet, in welchem ​​Kontext er den Benutzer wiederbeleben soll.

- (User *)userWithContext:(NSManagedContext *)context { 
    User *user = nil; 

    NSError *error; 
    NSFetchRequest *request = ...; 
    NSArray *userArray = [context executeFetchRequest:request error:&error]; 
    if (userArray == nil) { 
     NSLog(@“Error fetching user: %@“, error); 
    } else if ([userArray count] > 0) { 
     user = userArray[0]; 
    } 

    return user; 
} 

Und hier sind andere Gedanken über Ihre Code-Schnipsel.

Der Kontext, den Sie im Hauptverzeichnis Ihrer Operation erstellen, könnte ein Kind Ihres Hauptkontextes sein. Nach dem Speichern dieses untergeordneten Elements speichern Sie das übergeordnete Element (wenn Sie möchten, dass die Daten zu diesem Zeitpunkt in den Speicher übertragen werden). Wenn Sie die Eltern-Kind-Beziehung verwenden, müssen Sie nicht die Benachrichtigung zum Speichern von Benutzern abonnieren und Änderungen daraus zusammenführen.

Technisch können Sie nicht aus der Hauptfunktion des Vorgangs auf den permanenten Speicherkoordinator Ihres Hauptkontextes zugreifen. Weil es nur erlaubt ist, mit Kontexten aus einem Thread oder einer Queue zu arbeiten (beliebige Methoden aufzurufen). Und ich wette, dass Sie Ihren Hauptkontext im Hauptthread erstellen.

+0

Vielen Dank für die Antwort! Lassen Sie mich versuchen, Ihre Vorschläge schnell umzusetzen, und ich melde mich in ein paar Minuten bei Ihnen. –

+0

Okay, ich habe diese Änderungen vorgenommen, aber der Haupt-Thread ist immer noch deadlocks, während die Operation in dieser Zeile läuft: NSArray * userArray = [Kontext executeFetchRequest: Anfrage Fehler: & Fehler]; Das Thead zeigt __psynch_mutuxwait. –

+0

Das tritt natürlich auf, wenn eine andere Methode von einem anderen Ansichtscontroller versucht, den aktuellen Benutzer abzurufen. Ich habe den mainagedObjectContext von AppDelegate übergeben und ihn sogar mit dispatch_get_main_queue im Hauptthread aufgerufen. Irgendwelche Ideen? –

0

Ihr Problem ist diese Linien, denke ich;

 NSNotificationCenter* notificationCenter = [NSNotificationCenter defaultCenter]; 
    [notificationCenter addObserver:self 
          selector:@selector(mergeChanges:) 
           name:NSManagedObjectContextDidSaveNotification 
           object:localContext]; 

Da Benachrichtigungen Feuer in dem Thread, in dem die Benachrichtigung geschieht, Ihre -mergeChanges: Methode wird in dem Hintergrund-Thread aufgerufen wird. Ich nehme an, dass es auf self.managedObjectContext zugreift und fügt die Objekte hinzu, aber es tut es von einem Hintergrundthread. Also, ja, es wird abstürzen oder hängen.

Sie müssen sich nicht für diese Benachrichtigung registrieren, es sieht so aus, da Sie diesen Kontext nur einmal speichern. Sie können einfach warten, bis Sie den Befehl save aufrufen und dann eine Zusammenführung planen: für den Hauptthread.Gefällt mir:

 if (![localContext save:&error]) 
     NSLog(@"Error saving: %@", error); 

    [[NSOperationQueue mainQueue] addOperationWithBlock:^{ 
     [self mergeChanges:nil]; 
    }]; 

    NSLog(@"Completed sync!");