Question : Comment puis-je obtenir mon contexte enfant pour voir les modifications persistantes sur le contexte parent afin qu'ils déclenchent mon NSFetchedResultsController pour mettre à jour l'interface utilisateur?
Voici la configuration:
Vous avez une application qui télécharge et ajoute beaucoup de données XML (environ 2 millions d'enregistrements, chacun à peu près la taille d'un paragraphe de texte normal). Le fichier .sqlite devient d'environ 500 Mo de taille. L'ajout de ce contenu dans Core Data prend du temps, mais vous voulez que l'utilisateur puisse utiliser l'application pendant que les données se chargent dans le magasin de données de manière incrémentielle. Il doit être invisible et imperceptible pour l'utilisateur que de grandes quantités de données sont déplacées, donc pas de blocage, pas de trac: des défilements comme du beurre. Néanmoins, l'application est plus utile, plus il y a de données ajoutées, nous ne pouvons donc pas attendre indéfiniment que les données soient ajoutées au magasin de données de base. Dans le code, cela signifie que j'aimerais vraiment éviter un code comme celui-ci dans le code d'importation:
[[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.25]];
L'application est iOS 5 uniquement, donc l'appareil le plus lent qu'il doit prendre en charge est un iPhone 3GS.
Voici les ressources que j'ai utilisées jusqu'à présent pour développer ma solution actuelle:
Guide de programmation des données de base d'Apple: Importer efficacement des données
- Utilisez les pools de libération automatique pour réduire la mémoire
- Coût des relations. Importez à plat, puis corrigez les relations à la fin
- Ne demandez pas si vous pouvez l'aider, cela ralentit les choses d'une manière O (n ^ 2)
- Importer par lots: enregistrer, réinitialiser, vidanger et répéter
- Désactivez le gestionnaire d'annulation lors de l'importation
iDeveloper TV - Performances des données de base
- Utilisez 3 contextes: types de contexte maître, principal et confinement
iDeveloper TV - Mise à jour des données de base pour Mac, iPhone et iPad
- L'exécution de sauvegardes sur d'autres files d'attente avec performBlock accélère les choses.
- Le cryptage ralentit les choses, désactivez-le si vous le pouvez.
Importation et affichage de grands ensembles de données dans les données de base par Marcus Zarra
- Vous pouvez ralentir l'importation en donnant du temps à la boucle d'exécution actuelle, afin que les choses se passent bien pour l'utilisateur.
- Un exemple de code prouve qu'il est possible d'effectuer des importations volumineuses et de garder l'interface utilisateur réactive, mais pas aussi rapidement qu'avec 3 contextes et une sauvegarde asynchrone sur disque.
Ma solution actuelle
J'ai 3 instances de NSManagedObjectContext:
masterManagedObjectContext - Il s'agit du contexte contenant le NSPersistentStoreCoordinator et responsable de l'enregistrement sur le disque. Je fais cela pour que mes sauvegardes puissent être asynchrones et donc très rapides. Je le crée au lancement comme ceci:
masterManagedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
[masterManagedObjectContext setPersistentStoreCoordinator:coordinator];
mainManagedObjectContext - C'est le contexte que l'interface utilisateur utilise partout. Il s'agit d'un enfant de masterManagedObjectContext. Je le crée comme ceci:
mainManagedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
[mainManagedObjectContext setUndoManager:nil];
[mainManagedObjectContext setParentContext:masterManagedObjectContext];
backgroundContext - Ce contexte est créé dans ma sous-classe NSOperation qui est responsable de l'importation des données XML dans Core Data. Je le crée dans la méthode principale de l'opération et je le lie au contexte maître.
backgroundContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSConfinementConcurrencyType];
[backgroundContext setUndoManager:nil];
[backgroundContext setParentContext:masterManagedObjectContext];
Cela fonctionne vraiment très, TRÈS vite. Simplement en faisant cette configuration à 3 contextes, j'ai pu améliorer ma vitesse d'importation de plus de 10 fois! Honnêtement, c'est difficile à croire. (Cette conception de base doit faire partie du modèle standard de données de base ...)
Pendant le processus d'importation, j'enregistre 2 manières différentes. Tous les 1000 éléments que je sauvegarde dans le contexte d'arrière-plan:
BOOL saveSuccess = [backgroundContext save:&error];
Puis à la fin du processus d'importation, j'enregistre sur le contexte maître / parent qui, ostensiblement, pousse les modifications vers les autres contextes enfants, y compris le contexte principal:
[masterManagedObjectContext performBlock:^{
NSError *parentContextError = nil;
BOOL parentContextSaveSuccess = [masterManagedObjectContext save:&parentContextError];
}];
Problème : le problème est que mon interface utilisateur ne sera pas mise à jour tant que je n'aurai pas rechargé la vue.
J'ai un UIViewController simple avec un UITableView qui reçoit des données à l'aide d'un NSFetchedResultsController. Lorsque le processus d'importation est terminé, le NSFetchedResultsController ne voit aucun changement du contexte parent / maître et ainsi l'interface utilisateur ne se met pas à jour automatiquement comme j'ai l'habitude de voir. Si je fais sortir le UIViewController de la pile et le charge à nouveau, toutes les données sont là.
Question : Comment puis-je obtenir mon contexte enfant pour voir les modifications persistantes sur le contexte parent afin qu'ils déclenchent mon NSFetchedResultsController pour mettre à jour l'interface utilisateur?
J'ai essayé ce qui suit qui bloque simplement l'application:
- (void)saveMasterContext {
NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter];
[notificationCenter addObserver:self selector:@selector(contextChanged:) name:NSManagedObjectContextDidSaveNotification object:masterManagedObjectContext];
NSError *error = nil;
BOOL saveSuccess = [masterManagedObjectContext save:&error];
[notificationCenter removeObserver:self name:NSManagedObjectContextDidSaveNotification object:masterManagedObjectContext];
}
- (void)contextChanged:(NSNotification*)notification
{
if ([notification object] == mainManagedObjectContext) return;
if (![NSThread isMainThread]) {
[self performSelectorOnMainThread:@selector(contextChanged:) withObject:notification waitUntilDone:YES];
return;
}
[mainManagedObjectContext mergeChangesFromContextDidSaveNotification:notification];
}