0

Ich bin in einigen Schwierigkeiten mit Hintergrundthreads zum Aktualisieren von Daten in UIManagedDocument/Core Data. Insbesondere verwende ich eine NSFetchResultsController, um Kartenanmerkungen auf der Grundlage von geocodierten Daten aus einem Hintergrund-Thread zu aktualisieren (nach dem Zusammenführen zurück in meine Haupt-MOC), aber die Karte wird nie aktualisiert aufgrund der Art und Weise Daten in seine Filialen und/oder zu seinen mehrere MOCs (Eltern und Kind). Wenn ich die App schließe und erneut öffne, werden die Annotationen aufgefüllt, so dass irgendwann ein Commit an den persistenten Speicher erfolgt, aber es ist mir unklar, wie man einen solchen Commit erzwingt, der somit die NSFetchResultsController aktualisieren würde. Hier einige Code:Kerndaten: NSManagedObjectContext, NSFetchResultsController und UIManagedDocument

Der Hintergrund-Thread, der die MOC aktualisiert:

- (void) populateGPSCoordsInClubsInContext: (NSManagedObjectContext *) mainCtx 
{    
    dispatch_queue_t MapFetchQ = dispatch_queue_create("Google Map Data Fetcher", NULL); 
    dispatch_async(MapFetchQ, ^{ 

     NSManagedObjectContext * ctxThread = [[NSManagedObjectContext alloc] init]; 
     [ctxThread setPersistentStoreCoordinator:mainCtx.persistentStoreCoordinator]; 


     NSFetchRequest * request = [NSFetchRequest fetchRequestWithEntityName:@"Club"]; 
     request.predicate = [NSPredicate predicateWithFormat:@"inRegion.name=%@", self.name]; 
     NSError *error = nil; 

     NSArray * clubs = [ctxThread executeFetchRequest:request error:&error]; 

     NSLog(@"[%@] Fetching map data. Club count is %d", self.name, [clubs count]); 

     int delayCounter = 0; 

     for(Club * club in clubs) 
     { 
      if(![club.hasCoord boolValue] && club != nil) 
      { 
       delayCounter++; // to deal with google maps api's DoS protection    

       [club setLongitudeAndLattitudeFromGoogle]; 
       NSError * error; 

       if(![ctx save:&error]) 
       NSLog(@"[%@] Problem saving region to database.", self.name); 

      } 

      if(delayCounter == 8) 
      {    
       [NSThread sleepForTimeInterval:(NSTimeInterval)2.0]; 
       delayCounter = 0; 
      } 
     } 
    }); 
    dispatch_release(MapFetchQ); 
} 

Wenn diejenigen spart genannt werden, ergreife ich die Benachrichtigung über den Haupt-Thread (in meinem AppDelegate) wie folgt:

- (void) contextDidSave: (NSNotification *) notification 
{ 
    NSManagedObjectContext * ctx = [self.clubsDB managedObjectContext]; 
    [ctx mergeChangesFromContextDidSaveNotification:notification]; 

    NSArray * updates = [[notification.object updatedObjects] allObjects]; 

    for(Club * club in updates) // This never fires because updates never has objects 
    { 
     NSLog(@"*********** %@", club.name); 
    } 

    NSLog(@"[%@] %@", [self class], NSStringFromSelector(_cmd)); 

} 

Und ich habe meine geholt Ergebnisse Controller wie so gesetzt (das Prädikat stimmt, sind die Ergebnisse wie bei App Neustart erwartet, nachdem die Daten in den Laden begangen wurde):

-(void) setupFRC 
{ 

    NSFetchRequest * request = [NSFetchRequest fetchRequestWithEntityName:@"Club"]; 
    request.sortDescriptors = [NSArray arrayWithObject:[NSSortDescriptor sortDescriptorWithKey:@"name" ascending:YES]]; 
    request.predicate = [NSPredicate predicateWithFormat:@"inRegion.name=%@ AND hasCoord=%@",[self.clubsDB regionTitleAsString], [NSNumber numberWithBool:YES]]; // Follow the relationshop and only display clubs from THIS region. 

    //request.predicate = [NSPredicate predicateWithFormat:@"inRegion.name=%@",[self.clubsDB regionTitleAsString]]; 

    self.debug = YES; 

    self.fetchedResultsController = 
    [[NSFetchedResultsController alloc] initWithFetchRequest:request 
             managedObjectContext:self.clubsDB.managedObjectContext 
              sectionNameKeyPath:nil 
                cacheName:nil]; 
} 

Irgendwelche Ideen, wie ich das entsprechende MOC aktualisieren kann, damit der abgerufene Ergebniscontroller sich wie gewünscht verhält?

+0

Wer überhaupt? Völlig fest ..: / – Andrew

Antwort

0

Ich habe ein Problem mit der Art, wie UIManagedDocument seine Commits sendet. Die einzige Lösung, die ich mir vorstellen kann, besteht darin, die Verwendung von UIManagedDocument zu beenden und nur den Kontext aus dem PersistentStore zu verwenden, der in der standardmäßigen Master-Detail-Vorlage bereitgestellt wird.

edit: Nach weiteren Recherchen scheint es so, als ob UIManagedDocument keine Möglichkeit hat, Änderungen zu committen, daher ist es wahrscheinlich besser, dass Sie einen aus dem persistenten Speicher erstellten Kontext übergeben. Es scheint, dass es kein Zufall ist, dass Apple noch keinen verwendbaren Beispielcode für UIManagedDocument zur Verfügung gestellt hat. Ich würde bei der Standardvorlage bleiben.

Hier ist ein Link zu jemand ein ähnliches Problem konfrontiert, und ihre „Lösung“ - manchmal die beste Lösung ist es zu wissen, ist nicht ein: P

Core Data managed object does not see related objects until restart Simulator

0

Ich war tatsächlich in der Lage das lösen Problem. Der Trick bestand darin, sicherzustellen, dass der Kontext mit den Objekten gefüllt war, die ich bearbeitet hatte, bevor der FRC gesetzt wurde. Ehrlich gesagt, es war extrem esoterisch und die Tatsache, dass UIManagedDocument nicht wie erwartet funktioniert (oder sogar, wie die Dokumentation es erklärt) ist beunruhigend.

0

OK, ich werde Ihren Code bearbeiten, um Ihnen eine Vorstellung davon zu geben, wie er aussehen sollte. Ich nehme an, dass Sie den MOC aus dem UIManagedDocument in populateGPSCoordsInClubsContext übergeben. Beachten Sie, es gibt sehr wenig Unterschied in dem, was Sie bereits tun, aber wie wir wissen, kann eine Zeile Code macht den Unterschied ...

// NOTE: Make it clear you expect to work on a document... 
- (void) populateGPSCoordsInClubsInContext: (UIManagedDocument *) document 
{    
    dispatch_queue_t MapFetchQ = dispatch_queue_create("Google Map Data Fetcher", NULL); 
    dispatch_async(MapFetchQ, ^{ 

     NSManagedObjectContext * ctxThread = [[NSManagedObjectContext alloc] init]; 
     // NOTE: Make changes up into the context of the document 
     ctxThread.parentContext = document.managedObjectContext;  

     NSFetchRequest * request = [NSFetchRequest fetchRequestWithEntityName:@"Club"]; 
     request.predicate = [NSPredicate predicateWithFormat:@"inRegion.name=%@", self.name]; 
     NSError *error = nil; 

     NSArray * clubs = [ctxThread executeFetchRequest:request error:&error]; 

     NSLog(@"[%@] Fetching map data. Club count is %d", self.name, [clubs count]); 

     int delayCounter = 0; 

     for(Club * club in clubs) 
     { 
      if(![club.hasCoord boolValue] && club != nil) 
      { 
       delayCounter++; // to deal with google maps api's DoS protection    

       [club setLongitudeAndLattitudeFromGoogle]; 
       NSError * error; 

       // NOTE: This notifies the parent context of the changes. 
       if(![ctx save:&error]) 
       NSLog(@"[%@] Problem saving region to database.", self.name); 
       // NOTE: However, since a UIManagedDocument is an "auto-save" 
       // document, we need to tell it that is is dirty... 
       [document updateChangeCount:UIDocumentChangeDone]; 
      } 

      if(delayCounter == 8) 
      {    
       [NSThread sleepForTimeInterval:(NSTimeInterval)2.0]; 
       delayCounter = 0; 
      } 
     } 
    }); 
    dispatch_release(MapFetchQ); 
} 

Eine der kühlen Sachen über es auf diese Weise zu tun, ist dass Sie nicht einmal mit den Benachrichtigungen umgehen müssen (zumindest nicht aus Gründen der Konsistenz).

Wenn Sie wollen es auf die andere Weise tun, können Sie dies tun ...

 ctxThread.parentContext = document.parentContext;  

Sie würden dann nicht updateChangeCount für das Dokument aufrufen. Diese Änderungen würden in den übergeordneten Kontext und in die Datei übernommen. Sie müssen die Benachrichtigungen jedoch auch nicht verarbeiten, da sie bei zukünftigen Abrufen automatisch angezeigt werden. Natürlich, wenn Sie bei Änderungen aktualisieren möchten, könnten Sie immer noch Benachrichtigungen verarbeiten, aber das alles. Um sie auf Abruf zu sehen, müssen Sie nichts anderes tun.

Entgegen der landläufigen Meinung ist UIManagedDocument wirklich sehr effektiv und einfach (sobald Sie die Regeln kennen).