101 votes

Mise en œuvre rapide et efficace Core Data Import 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'application est 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
  • Les 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) manière
  • L'importation par Lots: enregistrer, de réinitialisation, les égoutter et répéter
  • Désactiver l'Annulation du Gestionnaire à 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 et iPad mise à Jour

  • L'exécution d'économiser sur d'autres files d'attente avec performBlock rend les choses rapidement.
  • Cryptage 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];

Ensuite, à 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 une 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];
}

47voto

Jody Hagins Points 15644

Vous devriez probablement vous enregistrez le maître de la MOC dans le progrès. Pas que MOC attendre jusqu'à la fin de l'enregistrer. Il a son propre thread, et il aidera à garder la mémoire vers le bas.

Vous avez écrit:

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

Dans votre configuration, vous disposez de deux enfants (le principal MOC et l'arrière-plan MOC), apparenté au "maître".

Lorsque vous enregistrez sur un enfant, il pousse les changements dans la société mère. Les autres enfants de la MOC va voir les données la prochaine fois qu'ils effectuent un fetch... ils ne sont pas explicitement notifié.

Ainsi, lorsque BG des sauvegardes de ses données est poussé à MAÎTRISER. Notez, cependant, qu'aucune de ces données sur le disque jusqu'à ce MAÎTRE sauve. En outre, tous les nouveaux éléments ne seront pas obtenir permanente IDs jusqu'à ce que le MAÎTRE enregistre sur le disque.

Dans votre scénario, vous êtes en tirant les données dans les PRINCIPAUX MOC par la fusion du MAÎTRE de l'enregistrement en cours l'DidSave de notification.

Cela devrait fonctionner, donc je suis curieux de savoir où il est "accroché." Je note que vous n'êtes pas en cours d'exécution sur les principaux MOC fil dans la manière canonique (au moins pas pour iOS 5).

Aussi, vous avez probablement seulement sont intéressés à la fusion de modifications du maître MOC (si votre inscription semble que c'est uniquement pour ça de toute façon). Si je devais utiliser la mise à jour-on-a-enregistrer-notification, je le ferais...

- (void)contextChanged:(NSNotification*)notification {
    // Only interested in merging from master into main.
    if ([notification object] != masterManagedObjectContext) return;

    [mainManagedObjectContext performBlock:^{
        [mainManagedObjectContext mergeChangesFromContextDidSaveNotification:notification];

        // NOTE: our MOC should not be updated, but we need to reload the data as well
    }];
}

Maintenant, pour ce qui est peut-être votre vrai problème concernant le coup... vous montrer deux appels différents pour enregistrer sur le maître. la première est bien protégée dans son propre performBlock, mais la seconde ne l'est pas (si on peut appeler saveMasterContext dans un performBlock...

Cependant, je voudrais aussi changer ce code...

- (void)saveMasterContext {
    NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter];    
    [notificationCenter addObserver:self selector:@selector(contextChanged:) name:NSManagedObjectContextDidSaveNotification object:masterManagedObjectContext];

    // Make sure the master runs in it's own thread...
    [masterManagedObjectContext performBlock:^{
        NSError *error = nil;
        BOOL saveSuccess = [masterManagedObjectContext save:&error];
        // Handle error...
        [notificationCenter removeObserver:self name:NSManagedObjectContextDidSaveNotification object:masterManagedObjectContext];
    }];
}

Notez, cependant, que le PRINCIPAL est un enfant de MASTER. Donc, il ne devrait pas avoir à fusionner les modifications. Au lieu de cela, il suffit de regarder pour les DidSave sur le maître, et juste récupère à nouveau! Les données sont assis dans votre parent déjà, juste en attente pour vous de le demander. C'est un des avantages d'avoir les données dans le parent en premier lieu.

Une autre alternative à considérer (et je serais intéressé à entendre parler de vos résultats, c'est beaucoup de données)...

Au lieu de faire l'arrière-plan MOC d'un enfant du MAÎTRE, font un enfant de la MAIN.

Obtenir cette. Chaque fois que le BG sauve, il obtient automatiquement poussé dans la MAIN. Maintenant, le PRINCIPAL est d'appeler la méthode save, puis par le maître d'appeler la méthode save, mais tous ceux qui sont en train de faire est de déplacer les pointeurs... jusqu'à ce que le maître enregistre sur le disque.

La beauté de cette méthode est que les données de l'arrière-plan MOC directement dans vos applications MOC (traverse ensuite être épargné).

Il y a quelques peine du pass-through, mais le gros du travail se fait dans le MAÎTRE quand il frappe le disque. Et si vous le coup de pied de ceux qui l'enregistre sur le maître avec performBlock, puis thread principal envoie juste au large de la demande, et retourne immédiatement.

S'il vous plaît laissez-moi savoir comment ça se passe!

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X