7
votes

Utilisation des données de base simultanément et de manière fiable

Je construis ma première application iOS, qui doit être assez simple, mais j'ai des difficultés à le faire suffisamment de balle pour que je me sente confiant dans l'App Store.

Brièvement, l'écran principal a Une vue sur la table, lors de la sélection d'une ligne, il segendra à une autre vue de la table qui affiche des informations pertinentes pour la ligne sélectionnée de manière maîtrise. Les données sous-jacentes sont extraites sous forme de données JSON d'un service Web une fois par jour, puis mis en cache dans un magasin de données de base. Les données précédentes à ce jour sont supprimées pour arrêter le fichier de base de données SQLite de la croissance indéfiniment. Toutes les opérations de persistance des données sont effectuées à l'aide de données de base, avec un nsfetchedresulttroller code> sous-aller la vue de la table de détail. P>

Le problème que je vois est que si vous passez rapidement entre les écrans de maître et de détail Plusieurs fois, tandis que des données fraîches sont récupérées, analysées et sauvegardées, l'application gèle ou se bloque complètement. Il semble y avoir une sorte de problème de race, peut-être dû à des données de base d'importation de données en arrière-plan tandis que le fil principal tente d'effectuer une récupération, mais je spécule. J'ai eu du mal à capturer des informations de collision significatifs, il s'agit généralement d'un SIGSEGV au fond de la pile de données principale. P>

Le tableau ci-dessous montre l'ordre réel des événements qui se produisent lorsque le contrôleur de vue de la table de détail est chargé: xxx pré>

une fois que le bloc d'achèvement de l'opération Afnet est déclenché lorsque les données JSON sont arrivées, un nsmanagedObjectContext code> est créé et transmis à un objet "importateur" qui analyse Les données JSON et enregistre les objets dans le magasin de données de base. L'importateur s'exécute à l'aide de la nouvelle méthode code> méthode introduite dans iOS 5: p>

NSManagedObjectContext *child = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
    [child setParentContext:self.managedObjectContext];        
    [child performBlock:^{
        // Create importer instance, passing it the child MOC...
    }];


0 commentaires

4 Réponses :


2
votes

Juste une idée architecturale:

Avec votre motif de rafraîchissement de données indiqué (une fois par jour, le cycle complet des données supprimé et ajouté), je serais motivé pour créer un nouveau magasin persistant chaque jour (c'est-à-dire nommé pour la date du calendrier), puis à l'achèvement de l'achèvement. NOTIFICATION, Demandez à la vue Table de configurer une nouvelle fetchedResultController associée au nouveau magasin (et probablement un nouveau MOC) et d'actualiser en utilisant cela. Ensuite, l'application peut (ailleurs, peut-être également déclenchée par cette notification) détruit complètement l'ancien magasin de données. Cette technique découle le traitement de la mise à jour à partir du magasin de données que l'application utilise actuellement et que le "commutateur" sur les nouvelles données peut être considéré comme considérablement plus atomic , car le changement arrive simplement commencer à pointer vers Les nouvelles données au lieu d'espérer que vous n'atteignez pas le magasin dans un état incohérent alors que de nouvelles données sont en cours d'écriture (mais ne sont pas encore terminées).

Évidemment, j'ai laissé des détails, mais j'ai tendance à penser que beaucoup de données modifiées tout en étant utilisées devraient être réinstallées afin de réduire la probabilité du type de crash que vous connaissez. < / p>

heureux de discuter plus loin ...


1 commentaires

J'aime cette idée, j'aime bien beaucoup . Il présente également l'avantage qu'un système de fichiers supprime le fichier SQLITE sera beaucoup plus rapide que de supprimer les données de base supprimez les objets gérés dans le graphique de l'objet, bien que, bien sûr, la performance de suppression n'a pas d'importance, car un magasin persistant différent sera utilisé quand même. Je vais donner cette approche un coup ce week-end.



3
votes

Pré-iOS 5, nous avons généralement deux nsmanagedObjectContextes : un pour le fil principal, un pour un fil d'arrière-plan. Le thread de fond peut charger ou supprimer des données, puis sauvegarder. Le nsmanagedObjectContextDididSacAntification a ensuite été passé (comme vous le faites) sur le fil principal. Nous avons appelé mergechangesfrommanagedObjectContextDidsAnotification: pour amener ceux dans le contexte principal du fil. Cela a bien fonctionné pour nous.

Un aspect important de celui-ci est que le Enregistrer: sur le thread de fond bloque jusqu'à après le MergechangesfrommanagedObjectContextDididsacotification: Finitions exécutées sur le fil principal (Parce que nous appelons des mergechanges ... de l'auditeur à cette notification). Cela garantit que le contexte de l'objet géré le fil principal voit ces changements. Je ne sais pas si vous besoin de faire cela si vous avez une relation parent-enfant, mais que vous avez fait dans l'ancien modèle pour éviter divers types de problèmes.

Je ne suis pas sûr de l'avantage d'avoir une relation parent-enfant entre les deux contextes. Il semble de votre description que le disque final sur le disque se produit sur le fil principal, ce qui n'est probablement pas idéal pour des raisons de performance. (Surtout si vous supprimez une grande quantité de données; le coût majeur de la suppression de nos applications s'est toujours produit lors de la sauvegarde finale sur le disque.)

Quel code êtes-vous en cours d'exécution lorsque les contrôleurs apparaissent / disparaissent qui pourrait causer des problèmes de données de base? Quels types de traces de pile voyez-vous l'accident sur?


1 commentaires

Merci. En fait, je n'utilise pas MergechangesfrommaniedObjectContextDididSacotification: Parce que dans l'utilisation normale, les nouvelles données apparaissent parfaitement bien sans elle. Cependant, je l'ai utilisé dans une version antérieure et eu les mêmes accidents lors de la commutation rapidement entre les deux écrans.



2
votes

Le problème principal que j'ai eu avec des données de base multi-filetés est d'accéder par inadvertance à un objet géré dans un fil / une file d'attente autre que celui créé par celui-ci.

J'ai trouvé un bon outil de débogage consiste à ajouter Nsasserts pour vérifier que les objets gérés créés dans votre contexte d'objet géré principal ne sont utilisés que là-bas et ceux créés dans un contexte d'arrière-plan ne sont pas utilisés dans le contexte principal.

Cela impliquera la sous-classement NSManèdeObjectContext et NSManageDObject:

  • Ajoutez un ivar à la sous-classe MOC et assignez-y la file d'attente qui a été créée.
  • Votre sous-classe MO doit vérifier que la file d'attente actuelle est la même que celle de sa propriété file d'attente de la MOC.

    Ce n'est que quelques lignes de code, mais à long terme peut vous empêcher de faire des erreurs difficiles à suivre autrement.


4 commentaires

Merci, mais je ne suis pas sûr comment obtenir la file d'attente de la MOC puisqu'elle est instanciée à l'aide de initwithconcurrencyype: NSprivateQueconCurencyType Ce qui signifie qu'il crée sa propre file d'attente privée.


Que diriez-vous de Dispatch_Get_Current_Queue à l'intérieur de votre performance:


Cette file d'attente est privée; Essayer d'y accéder directement est quelque chose que les pommes appellent comme une mauvaise idée.


@Jesserusak True, mais je ne vous suggère pas de faire quoi que ce soit, utilisez-le simplement pour comparer la file d'attente que la MOC d'un objet géré a été créée sur / avec.



2
votes

NsfetchedResultStroller a été prouvé un peu sensible aux suppressions massives, de sorte que je commencerais à creuser en premier.

Ma question initiale est, comment se trouvent la récupération et la recharge de la TableView liées au début de l'opération Supprimer. Y a-t-il une chance que le bloc de suppression sauvegardera l'enfant MOC alors que le NsfetchedResultSluTroller est toujours chercher ou non?

est-il possible que lorsque vous passez de la vue de détail à la maîtrise, puis de retour à la vue détaillée, il y aura plusieurs tâches d'arrière-plan simultanées en cours d'exécution? Ou récupérez-vous toutes les données du service Web à une fois non seulement pertinentes pour une rangée particulière?

Une alternative pour rendre cela plus robuste consiste à utiliser un motif similaire à ce que uimanagedDocument utilise:

Au lieu d'utiliser un parent MOC en tant que Type de fil principal, UimanageDDocument crée en fait la MOC principale en tant que file d'attente privée et rend l'enfant MOC disponible pour vous utiliser sur le fil principal. Le bénéfice ici est que tous les E / S se poursuivent en arrière-plan et sauvent au parent MOC n'interfèrent pas du tout que l'enfant MOC jusqu'à ce que l'enfant MOC soit explicitement fait savoir à leur sujet. C'est parce que sauvegardez des changements d'enfant au parent et non l'inverse.

Donc, si vous avez fait vos suppressions sur une file d'attente parentale qui est privée, cela ne se retrouverait pas dans le NsfetchedResultSluTroller Portée du tout. Et puisqu'il s'agit d'anciennes données, c'est en fait la manière préférée.

Une alternative que j'offre est d'utiliser trois contextes:

MOC MOC ( NSprivateQueconCurencyType )

  • responsable du magasin persistant et de la suppression des anciennes données.

    enfant MOC A ( NSMainQuEConCurencyType )

    • Responsable de tout ce qui concerne l'interface utilisateur liée à l'interface utilisateur et NsfetchedResultSluTroller

      enfant MOC B ( NsprivateQueconCurencyType , enfant d'enfant MOC A)

      • responsable de l'insertion de nouvelles données et de la valider à l'enfant MOC Une fois terminé.

0 commentaires