jeudi 8 mars 2012

Un autre Gotcha TableView / NSFetchedResultsController

Si vous avez suivi ce blog, pour toute longueur de temps, vous savez que j'ai été de verrouillage cornes avec NSFetchedResultsController et périodiquement libérant versions mises à jour du modèle de navigation basé sur Core Data Xcode pour répondre aux différents problèmes, des incohérences et pièges que j'ai découverts au cours de mon combat.

Since Plus l'iPhone 3 de développement a été libéré, j'ai été faire des rapports sporadiques d'un problème avec le chapitre 4 version de l'application des données de base qui, jusqu'à hier soir, je n'avais pas été capable de se reproduire. Un lecteur a pu enfin me faire parvenir des instructions précises, et voilà, j'ai été capable de reproduire le problème.

Donc, j'ai commencé à parcourir le code, et a constaté que dans certaines situations (dont les paramètres, je n'ai pas totalement compris encore), mon code est d'essayer d'insérer deux sections dans la table quand une seule nouvelle section est requis par la mise à jour. Il se produit quand une valeur utilisée dans le chemin de clé section est changé, mais pas toujours quand ça arrive.

Qu'est-ce qui se passe est, dans le contrôleur: didChangeSection: atIndex: forChangeType:, je averti d'une nouvelle section étant insérée dans le contrôleur de chercher les résultats et insérez une section correspondant à l'endroit approprié dans le tableau, comme ceci:

    [self.tableView insertSections:[NSIndexSet indexSetWithIndex:sectionIndex] withRowAnimation:UITableViewRowAnimationFade];

Tout va bien, non? Mais alors, dans le contrôleur: didChangeObject: atIndexPath: forChangeType: newIndexPath: qui tire après, j'ai du code qui vérifie que le nombre de correspondances entre le contrôleur sections chercher les résultats et la vue du tableau. Ce code est nécessaire parce que dans certaines situations, NSFetchedResultController ne pas dire son délégué, si une nouvelle section a été créée. C'est un chèque assez simple, je viens de trouver le nombre de sections dans le contrôleur de chercher les résultats et dans le tableau et quand ils ne correspondent pas, je insérer une nouvelle section dans la table.

    NSUInteger tableSectionCount = [self.tableView numberOfSections];
NSUInteger frcSectionCount = [[controller sections] count];
if (frcSectionCount != tableSectionCount)
[self.tableView insertSections:[NSIndexSet indexSetWithIndex:[newIndexPath section]] withRowAnimation:UITableViewRowAnimationNone];

Et cela fonctionne la plupart du temps. Mais, parfois, elle n'existe pas. La nature sporadique, il est difficile à déboguer, mais j'ai finalement réussi à progresser dans le code quand il se passait. Dans le contrôleur: didChangeSection: atIndex: forChangeType: avant la ligne de code qui insère une nouvelle section, j'ai vérifié le nombre de sections dans la vue du tableau. Il y avait cinq.

Puis, après la ligne de code qui a inséré l'article, j'ai vérifié à nouveau. Il y avait encore cinq ans.

Sonne comme un bug dans le code d'Apple, non? En fait, il n'est pas. Il est documenté comportement.

La documentation pour insertRowsAtIndexPath: withRowAnimation: sur UITableView dit:
UITableView reporte toutes les insertions de lignes ou de sections jusqu'à ce qu'il ait manipulé les suppressions de lignes ou de sections. Cela se produit indépendamment de la commande de l'insertion et appelle la méthode de suppression.
Cela me laisse avec un bon casse-tête. Depuis mon code n'est pas directement la gestion de la table, mais NSFetchedResultsController reporte certaines tâches à son délégué, qui is mon code, je n'ai pas un moyen facile (que je connais encore) afin de déterminer quand l'insertion de la ligne du code précédent va être reporté par conséquent la cause de mes plus tard, vérifiez à l'échec.

Une solution, qui se sent kludgey, serait d'avoir une variable d'instance BOOL à suivre quand une méthode déléguée appel antérieur a inséré une ligne. Je n'aime pas cette solution, bien, donc je suis à la recherche d'une meilleure option à intégrer dans mes méthodes déléguée génériques.

Je vous tiendrai tous les jour sur ma progression, mais si vous avez des idées comment je peux déterminer si il ya un insert en attente dans une table, n'hésitez pas à les partager dans les commentaires.

Mise à jour 1: Il est un organisme privé mutables tableau appelé _insertItems qui détient les insertions différé. Même si c'est publié dans le fichier d'entête, je pense que l'accès à ce utiliser directement serait techniquement considéré comme une API privée. Les variables d'instance avec un trait de soulignement sont considérées comme privées par Apple, même s'ils sont publiés dans un fichier d'entête.

Mise à jour 2/>: Je possède une version fonctionnant illicites! Malheureusement, je ne peux pas l'utiliser car il nécessite l'accès aux variables d'instance privées de UITableView. Une fois que Rapporteur de bogues d'Apple est de retour, je vais mettre dans une demande d'amélioration de l'information dont j'ai besoin rendue publique, mais je vais probablement devoir trouver une autre solution provisoire, et il sera probablement hacky .

Pour les curieux, ce que j'ai fait était de créer une catégorie sur UITableView qui a ajouté cette méthode:

- (NSUInteger)numberOfPendingSectionInserts
{
NSUInteger ret = 0;
for (id /* UIUpdateItem */ oneUpdateItem in _insertItems)
{
if ([oneUpdateItem isSectionOperation])
ret++;
}

return ret;
}

Maintenant, ne pas l'utiliser dans vos applications, comme vous aurez rejetée de l'App Store. UIUpdateItem n'est pas une classe publique, et _insertItems n'est pas une variable d'instance publique (même si c'est contenu dans un fichier en-tête du public). Si cette information soit rendue disponible, alors je serais capable de faire un contrôle de cohérence plus robuste qui permettrait d'éliminer le problème d'insertion double:

    NSUInteger tableSectionCount = [self.tableView numberOfSections] + [self.tableView numberOfPendingSectionInserts];
NSUInteger frcSectionCount = [[controller sections] count];
if (frcSectionCount != tableSectionCount)
[self.tableView insertSections:[NSIndexSet indexSetWithIndex:[newIndexPath section]] withRowAnimation:UITableViewRowAnimationNone];


Aucun commentaire: