NSFetchedResultsController: Fetch in einem hintergrund-thread
Habe ich eine mehr oder weniger grundlegende UITableViewController
mit einem NSFetchedResultsController
. Die UITableViewController
geschoben wird, ist auf die navigationController's
stack. Aber die push-animation nicht glatt, da der Abruf NSFetchedResultsController
erfolgt über den Haupt-thread, und daher blockiert das UI.
Meine Frage ist: Wie kann ich die abrufen der NSFetchedResultsController
in einem hintergrund-thread, damit die animation zu glatt?
Den NSFetchedResultsController
und die delegate-Methoden wie folgt Aussehen:
- (NSFetchedResultsController *)fetchedResultsController
{
if (_fetchedResultsController != nil) {
return _fetchedResultsController;
}
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
//Edit the entity name as appropriate.
NSEntityDescription *entity = [NSEntityDescription entityForName:@"GPGrade" inManagedObjectContext:self.managedObjectContext];
[fetchRequest setEntity:entity];
//Set the batch size to a suitable number.
[fetchRequest setFetchBatchSize:20];
//Set predicate
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"parent == %@", self.subject];
[fetchRequest setPredicate:predicate];
//Edit the sort key as appropriate.
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:@"name" ascending:YES];
NSArray *sortDescriptors = @[sortDescriptor];
[fetchRequest setSortDescriptors:sortDescriptors];
//Edit the section name key path and cache name if appropriate.
//nil for section name key path means "no sections".
NSFetchedResultsController *aFetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:self.managedObjectContext sectionNameKeyPath:nil cacheName:@"SubjectMaster"];
aFetchedResultsController.delegate = self;
self.fetchedResultsController = aFetchedResultsController;
NSError *error = nil;
if (![self.fetchedResultsController performFetch:&error]) {
//Replace this implementation with code to handle the error appropriately.
//abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
abort();
}
return _fetchedResultsController;
}
- (void)controllerWillChangeContent:(NSFetchedResultsController *)controller
{
[self.tableView beginUpdates];
}
- (void)controller:(NSFetchedResultsController *)controller didChangeSection:(id <NSFetchedResultsSectionInfo>)sectionInfo
atIndex:(NSUInteger)sectionIndex forChangeType:(NSFetchedResultsChangeType)type
{
switch(type) {
case NSFetchedResultsChangeInsert:
[self.tableView insertSections:[NSIndexSet indexSetWithIndex:sectionIndex] withRowAnimation:UITableViewRowAnimationFade];
break;
case NSFetchedResultsChangeDelete:
[self.tableView deleteSections:[NSIndexSet indexSetWithIndex:sectionIndex] withRowAnimation:UITableViewRowAnimationFade];
break;
}
}
- (void)controller:(NSFetchedResultsController *)controller didChangeObject:(id)anObject
atIndexPath:(NSIndexPath *)indexPath forChangeType:(NSFetchedResultsChangeType)type
newIndexPath:(NSIndexPath *)newIndexPath
{
UITableView *tableView = self.tableView;
switch(type) {
case NSFetchedResultsChangeInsert:
[tableView insertRowsAtIndexPaths:@[newIndexPath] withRowAnimation:UITableViewRowAnimationTop];
break;
case NSFetchedResultsChangeDelete:
[tableView deleteRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationRight];
break;
case NSFetchedResultsChangeUpdate:
//[self configureCell:(GPSubjectOverviewListCell *)[tableView cellForRowAtIndexPath:indexPath] atIndexPath:indexPath];
break;
case NSFetchedResultsChangeMove:
[tableView deleteRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationFade];
[tableView insertRowsAtIndexPaths:@[newIndexPath] withRowAnimation:UITableViewRowAnimationFade];
break;
}
}
- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller
{
[self.tableView endUpdates];
}
- Wollte nicht, diese in die Antwort - bist du dir sicher, dass die Core-Daten abrufen, die das problem verursacht? Geht das problem Weg, wenn Sie nicht instanziieren, die die gefundenen Ergebnisse-Controller?
Du musst angemeldet sein, um einen Kommentar abzugeben.
Der Regel mit Core Data ist eine Managed-Object-Context-pro-thread und ein thread pro MOC. Mit diesem im Verstand, den Sie brauchen, um den Abruf für die gefundenen Ergebnisse-Controller auf dem main-thread, da dies der thread, der Interaktion mit der FRC Verwalteten Objekte. (Siehe Core Data Programming Guide - Parallelität mit Core Data)
Wenn Sie performance-Probleme mit der animation, die Sie sollten wahrscheinlich betrachten Möglichkeiten, um sicherzustellen, dass der Abruf durchgeführt wird, bevor oder nachdem die Ansicht geschoben wird. Normalerweise würden Sie die Holen in der view controller ist
viewDidLoad:
und der navigation-controller würde nicht schieben Sie die Ansicht, bis der Abruf beendet ist.TL;DR; Es gibt keinen guten Grund für die Verwendung von einem Kontext auf das Haupt-queue.
Absolut.
NSFetchedResultsController
verwendet werden kann, mit einer privaten Warteschlange Kontext. Es ist in der Tat sehr glücklich und leistungsfähig ist, wenn er so tut. Es ist ein bug , die verhindert, dassNSFetchedResultsController
von der Verwendung des cache, wenn Sie eine private Warteschlange, aber der cache nicht gewinnen Sie so viel, wie in iOS 3.0. Legen Sie einecacheName
im nil, und Sie werden in Ordnung sein.1.
Einen Kontext schaffen, mitNSPrivateQueueConcurrencyType
. Vorzugsweise nicht die, die Sie verwenden für die IO.2.
Erstellen der gefundenen Ergebnisse-controller mit, dass der Kontext, und einen cache-name nil.3.
Führen Sie Ihre ersten Abruf aus einemperformBlock:
block:4.
Alle Ihre Delegierten Rückrufe passieren wird, aus dem Kontext der Warteschlange. Wenn Sie diese verwenden, um Ansichten aktualisieren, so tun Sie es durch Versand an die main queue wie Sie oben sehen.5.
...6.
Gewinn!Lesen Sie mehr über dieses hier.
numberOfRowsInSection
. Die sectionInfo gibt die gleiche Anzahl von Objekten, nachdem ich ein Objekt eingefügt, um den Kontext. Gibt es da eine spezielle threading-tricks umgesetzt werden solltennumberOfRowsInSection
beim Zugriff auf eine FRCsectionInfo
?beginUpdates
wurden nicht geschlossen mitendUpdates
NSFetchedResultsController
aus dem Haupt-thread. Zum Beispiel[fetchedResultsController objectAtIndexPath:]
,fetchedResultsController.sections.count
usw. Aber wenn Ihr NSFetchedResultsController erstellt wurde, auf einem hintergrund-thread und hört änderungen zu den managed object context, kann es sein, aktualisieren sich eigenständig, was in den Haupt-thread.objectAtIndexPath:
innenwillDisplayCell:forRowAtIndexPath:
, das Auftritt, im Haupt-thread. Woher wissen Sie, dassobjectAtIndexPath:
zurückkehren wird konsequent zwischen den aufrufen derwillDisplayCell:forRowAtIndexPath:
? Es scheint mir, die gefundenen Ergebnisse Steuerung lässt sich ändern unter Ihnen auf unvorhersehbare Weise. Bedenken Sie: kehren Sie zurückfetchedResultsController.sections.count
um die Tabelle anzuzeigen. Es fordert Sie dann für die Zelle in der ersten index-Pfad. Aber heimlich im hintergrund-thread alles gelöscht wurde.objectAtIndexPath:
Rückkehr desselben Objekts für die verschiedenen Anrufungen derwillDisplayCell...
.objectAtIndexPath:
, es ist jedenNSFetchedResultsController
API, die Sie am Ende Zugriff aus dem Haupt-thread.sections
,section.numberOfObjects
usw. All dies kann ändern, unter Sie, wenn das verwaltete Objekt-Kontext ändert sich im hintergrund.UITableView
geht, rufen Sie IhrennumberOfSectionsInTableView
,numberOfRowsInSection
,cellForRowAtIndexPath
in Folge. Überlegen Sie, was passieren könnte, wenn zwischen jedem dieser Aufrufe, wird der Inhalt derNSFetchedResultsController
ist die gleichzeitige änderung in einem hintergrund-thread. Wie kann das möglich sein thread-safe?performBlockAndWait:
Betrieb an einer seriellen queue, mit dem VORBEHALT, dassperformBlockAndWait:
ablaufinvariant ist. Wie beschrieben in der Antwort (und Artikel), Zugriff auf alles Eigentum, die durch den Kontext geschehen sollte, durch diese Methoden - das sorgt für thread-Sicherheit.UITableView
ruftnumberOfSectionsInTableView
,numberOfRows
, undcellForRowAtIndexPath
in Folge in der main-thread. Zwischen jedem dieser Aufrufe anderer code sein könnte, der Ausführung der hintergrund-thread, dass änderungen der zugrunde liegenden Daten und ungültig, was du zurückgegeben. Sie können nicht garantieren, dass dieNSFetchedResultsController
hat sich nicht geändert zwischen der Zeit, die Sie sagen, dieUITableView
"ich habe 3 Abschnitte" und die Zeit es fordert Sie für die Anzahl der Zeilen, die im ersten Abschnitt.numberOfSectionsInTableView
brauchen würde, um den Zugriff auf diesectionInfo
desNSFetchedResultsController
Instanz und einen Wert zurückgeben. Es würde tun mit einem synchronen Aufruf aus der main queue auf den Kontext der Warteschlange und gibt das Ergebnis zurück an die main queue. Unabhängig davon, ob änderungen vorgenommen werden, um den Kontext, die Auswirkungen auf dieNSFetchedResultsController
die Stellvertretung informiert wird, die - synchron. Es ist die Verantwortung delegieren zu aktualisieren, die den Zustand der tableview entsprechend.numberOfSectionsInTableView
. Neben derUITableView
AnrufenumberOfRowsInSection
. Zwischen diesen beiden Gesprächen gibt es die Gelegenheit für dieNSFetchedResultsController
verändert zu haben, sagen, indem ein (oder mehrere) Abschnitte. Der Haupt-thread ist immer noch die Ausführung derUITableView
's Logik zum füllen der 10 Abschnitte, die Sie gesagt hatte. Er bittet Sie, für die Anzahl der Zeilen in jedem Abschnitt, von denen einige möglicherweise nicht mehr vorhanden. Sehen Sie das problem? DieNSFetchedResultsController
delegieren, ist irrelevant, weil kein anderer code ausgeführt hat, die in der Haupt-thread.UITableView
ist in der Mitte einrichten (weil Sie genanntreloadData
zum Beispiel), wird es nicht unterbrochen zu werden zwischen den aufrufennumberOfSectionsInTableView
undnumberOfRowsInSection
. Aber Ihre Daten-Quelle, seit es existiert, in einem anderen thread,UITableView
ausführt, deren setup-code in Reaktion aufreloadData
und ruftnumberOfSectionsInTableView
undnumberOfRowsInSection
gegen Ihre Datenquelle ein, es gibt keine Möglichkeit, Sie können eine beliebige andere code ausführen zwischen. Überprüfen können Sie dies selbst, indem Sie breakpointing durch.reloadData
wird aufgerufen, auf dem Haupt-thread und dem privaten Umfeld ist gleichzeitig geändert in einen hintergrund-thread, dadurch entsteht eine race condition, wo dieUITableView
undNSFetchedResultsController
bekommen kann out of sync, die manifestiert sich als dieUITableView
beantragt, ein Abschnitt oder index-Pfad, der nicht mehr existiert. (Im Grunde machen die privaten Kontext kontinuierlich einfügen/entfernen von Objekten und rufen Sie dannreloadData
im Haupt-thread. Es wird brechen.)Können Sie gehen durch diese sehr schön post auf Kern-Daten, die mit Multi-Threading Verhalten.
Hoffe es hilft..!!