La mise en œuvre Rapide et Efficace de Base de Données d'Importation sur iOS 5

Question: Comment puis-je obtenir mon enfant en contexte pour voir les changements ont persisté sur le contexte parent, de sorte qu'ils déclenchent mon NSFetchedResultsController de 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).fichier sqlite devient environ 500 MO en taille. L'ajout de ce contenu dans la Base de Données prend du temps, mais vous voulez que l'utilisateur soit en mesure d'utiliser l'application pendant les chargements de données dans la banque de données de manière incrémentale. Il faut qu'il soit invisible et imperceptible pour l'utilisateur que de grandes quantités de données sont déplacés, donc pas d'accroche, pas de nervosité: défile comme dans du beurre. Encore, l'application est plus utile, le plus de données est ajouté à cela, nous ne pouvons pas attendre une éternité pour que les données soient ajoutés à la Base de Données du magasin. Dans le code, ce qui signifie que je voudrais vraiment éviter ce type de code dans le code d'importation:

[[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.25]];

L'app iOS 5 uniquement si l'unité la plus lente qu'il doit soutenir un iPhone 3GS.

Voici les ressources que j'ai utilisé jusqu'à présent pour développer ma solution actuelle:

Apple de Base de Données Guide de Programmation: Efficacement l'Importation de Données

  • Utilisation Autorelease Piscines pour garder la mémoire vers le bas
  • Relations De Coût. À l'importation, à plat, puis le patch des relations à la fin
  • Ne demande pas si vous pouvez l'aider, il ralentit les choses dans un temps O(n^2) de manière
  • Importation en Lots: enregistrer, de réinitialisation, les égoutter et répéter
  • Désactiver l'Annulation du Gestionnaire sur l'importation

iDeveloper TV - Base de Données de Performances

  • 3 Contextes: Maître Principal et le Confinement types de contexte

iDeveloper TV - Base de Données pour Mac, iPhone & iPad mise à Jour

  • Cours d'exécution permet d'économiser sur d'autres files d'attente avec performBlock rend les choses rapidement.
  • De chiffrement ralentit les choses, de le désactiver si vous le pouvez.

L'importation et l'Affichage de Grands Ensembles de Données dans la Base de Données par Marcus Zarra

  • Vous pouvez ralentir l'importation en donnant du temps à l'exécution de la boucle,
    donc, les choses se sentir lisse à l'utilisateur.
  • Exemple de Code prouve qu'il est possible de faire de grandes importations et de garder l'INTERFACE utilisateur réactive, mais pas aussi rapide qu'avec 3 contextes et async l'enregistrer sur le disque.

Ma Solution Actuelle

J'ai eu 3 cas de NSManagedObjectContext:

masterManagedObjectContext - C'est le contexte qui est le NSPersistentStoreCoordinator et est responsable de la sauvegarde sur le disque. Je le fais donc ma sauve peut être asynchrone et donc très rapide. J'ai créer sur lancer comme ceci:

masterManagedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
[masterManagedObjectContext setPersistentStoreCoordinator:coordinator];

mainManagedObjectContext - C'est le contexte de l'INTERFACE utilisateur utilise partout. C'est un enfant de la masterManagedObjectContext. J'ai créer comme ceci:

mainManagedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
[mainManagedObjectContext setUndoManager:nil];
[mainManagedObjectContext setParentContext:masterManagedObjectContext];

backgroundContext - Ce contexte est créé dans mon NSOperation sous-classe qui est responsable de l'importation de données XML dans la Base de Données. J'ai créer dans le fonctionnement de la méthode principale et lien vers le maître de la situation.

backgroundContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSConfinementConcurrencyType];
[backgroundContext setUndoManager:nil];
[backgroundContext setParentContext:masterManagedObjectContext];

Cela fonctionne très, TRÈS vite. Tout simplement en faisant ce 3 contexte de l'installation, j'ai pu améliorer ma vitesse de l'importation de plus de 10 fois! Honnêtement, c'est difficile à croire. (Cette conception de base devrait faire partie de la norme de Base du modèle de Données...)

Pendant le processus d'importation-je économiser de 2 façons différentes. Tous les 1000 articles que j'ai enregistrer sur le contexte:

BOOL saveSuccess = [backgroundContext save:&error];

Puis à la fin du processus d'importation, je l'ai enregistrer sur le maître/contexte parent qui, en apparence, pousse des modifications à l'autre enfant de contextes, y compris le contexte principal:

[masterManagedObjectContext performBlock:^{
   NSError *parentContextError = nil;
   BOOL parentContextSaveSuccess = [masterManagedObjectContext save:&parentContextError];
}];

Problème: Le problème est que mon INTERFACE ne sera pas de mise à jour jusqu'à ce que je recharger la vue.

J'ai un simple UIViewController avec un UITableView qui est de la fed de données à l'aide d'un NSFetchedResultsController. Lorsque le processus d'Importation est terminé, l'NSFetchedResultsController vois pas de changements de la part du parent/master contexte et pour que l'INTERFACE utilisateur n'est pas automatiquement mise à jour, comme je suis habitué à voir. Si je pop le UIViewController hors de la pile et de le charger à nouveau toutes les données sont là.

Question: Comment puis-je obtenir mon enfant en contexte pour voir les changements ont persisté sur le contexte parent, de sorte qu'ils déclenchent mon NSFetchedResultsController de mettre à jour l'INTERFACE utilisateur?

J'ai essayé ce qui suit, qui se contente d'être 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];
}
  • +1000000 pour le mieux formé, plus préparés question. J'ai une réponse trop... Il va prendre quelques minutes pour type il bien...
  • Quand vous dites que l'application est suspendue, où est-il? Qu'est-il en train de faire?
  • Désolé de mettre ce après une longue période de temps. Pouvez-vous préciser ce que signifie "l'Importation, à plat, puis le patch des relations à la fin" signifie? N'avez-vous pas encore avoir que des objets en mémoire afin d'établir des relations? Je suis en train de mettre en œuvre une solution très similaire à la vôtre et je ne pouvais vraiment utiliser un peu d'aide pour abaisser l'empreinte mémoire.
  • Voir l'Apple Docs liés à un début de cet article. Elle explique cette. Bonne chance!
  • C'est une grande installation impliquant 3 Gpm, j'ai mis en place dans mon projet, cependant je peux te demander comment fais-tu pour éviter le blocage de la persistance de la boutique tout le masterManagedObjectContext est l'économie? Je vous remercie de votre compréhension stackoverflow.com/questions/14340617/...
  • Très bonne question et j'ai ramassé quelques trucs à partir de la description que vous avez fourni votre configuration
  • Excellente question! +1

InformationsquelleAutor David Weiss | 2012-05-10