web-dev-qa-db-fra.com

Problème spécifique à iOS 12: corruption des données binaires du stockage externe de Core Data

J'ai passé la meilleure partie d'une journée de travail à essayer de résoudre ce problème.

Contexte

J'ai un modèle de données de base simple, avec des livres et des séances de lecture. Les livres ont des couvertures (images) qui sont stockées sous forme de données binaires avec "Autorise le stockage externe".

Sur iOS 11.4 et versions ultérieures, tout fonctionne bien tout le temps. Lorsque j'enregistre une nouvelle session, tout est mis à jour correctement.

Problème

Depuis iOS 12, lorsque je crée une nouvelle session de lecture et que je la lie au livre, environ toutes les secondes , les données de base génèrent une instruction SQL qui met également à jour le champ de couverture du livre, ce qui entraîne parfois une mauvaise référence (au fichier sur le disque), ce qui entraîne souvent que la couverture est nulle lors du redémarrage de l'application, et presque crée toujours une copie en double de la pochette sur le disque (comme on peut le voir dans le dossier _EXTERNAL_DATA du simulateur).

Le contexte et les objets en mémoire restent corrects (et tout est donc correct dans l'interface utilisateur), jusqu'à ce que l'application soit redémarrée, puis la couverture est souvent nulle .

spécifique à iOS 12

Sur iOS 12, je peux reproduire de manière déterministe l'erreur dans le simulateur, sur des appareils physiques, et les utilisateurs ont également signalé l'erreur. Je ne peux pas reproduire l'erreur sur iOS 11.4, et aucun utilisateur n'a signalé l'erreur antérieure à iOS 12.

mesures prises

  • J'ai activé "-com.Apple.CoreData.ConcurrencyDebug 1", Il ne devrait donc pas être que j'accède à quoi que ce soit à partir de la mauvaise file d'attente. J'ai également activé "-com.Apple.CoreData.SQLDebug 3" Pour que je puisse voir exactement ce qui est écrit.

  • J'ai vérifié que l'instance de Book (et donc la couverture) n'est pas modifiée par mon code avant l'association avec la nouvelle session en vérifiant hasChanges, juste avant de faire la fonction newSession.book = book Et context.save().

  • Pour être sûr à 100% que je ne touche la propriété de couverture sur aucun fil, j'ai court-circuité mes getters et setters pour cette propriété. Pas d'amélioration.

  • J'ai essayé d'utiliser objectID pour demander une instance du livre juste avant l'association et l'enregistrer. Pas d'amélioration.

  • J'ai même essayé l'option où le contexte conserve des références fortes à tous les objets, juste pour m'assurer qu'il ne s'agissait pas d'une sorte de problème de gestion de la mémoire. Pas d'amélioration.

Question

Des idées pour les prochaines étapes?

Mise à jour du statut

Il s'agit d'un défaut dans iOS 12. Voir la réponse acceptée ci-dessous pour une description détaillée d'une solution de contournement acceptable.

27
zendo

Mise à jour: Le problème sous-jacent des données de base semble être résolu dans iOS 12.1 (vérifié dans beta 4). Nous conserverons la solution de contournement décrite ci-dessous dans notre application et ne recommanderons pas d'utiliser l'option Stockage externe de sitôt.


Après avoir parlé à Apple ingénieurs et déposé le Radar mentionné ci-dessus , nous ne pouvions pas attendre un correctif, nous avons donc pris le coup et sommes passés à stocker des fichiers sur le système de fichiers et le gérer directement nous-mêmes.

Une autre alternative que nous avons envisagée était la migration de notre modèle pour ne pas autoriser le stockage externe pour les BLOB, mais je ne sais pas quel impact cela aurait eu sur les performances et j'étais également préoccupé par une migration de modèle au moment où cette partie d'iOS semble être instable, surtout après avoir lu des histoires comme celle-ci dans le passé: Core Data: ne stockez pas de gros fichiers en tant que données binaires - Alexander Edge - Medium

Ce n'était pas trop pénible d'implémenter nous-mêmes le stockage local. Vous avez juste besoin d'avoir un identifiant unique pour chaque enregistrement que vous pouvez utiliser pour créer un nom de fichier afin de pouvoir mapper des fichiers sur des enregistrements. Nous avons ajouté une extension à notre sous-classe d'objets gérés avec des méthodes pour lire, écrire et supprimer les fichiers. Maintenant, au lieu d'appeler par ex. article.photo = image.pngData(), nous devons maintenant appeler quelque chose comme article.savePhoto(image.pngData()) puis nous faisons de même lorsque nous voulons récupérer l'image. Vous pouvez également ajouter du code à ces méthodes pour prendre en charge la compatibilité descendante avec toutes les images actuellement stockées dans Core Data.

La suppression était un peu plus délicate car nos objets sont supprimés de plusieurs endroits dans le code, y compris les suppressions en cascade. Au final, j'ai choisi de le faire dans la méthode prepareForDeletion de l'objet géré mais ce n'est pas idéal. Il y a beaucoup de discussions sur la meilleure façon de l'implémenter ici: cacao - Comment gérer le nettoyage des données externes lors de la suppression de non enregistré Objets Core Data? - Débordement de pile

Enfin, pour éviter que notre application ne se bloque lorsqu'un attribut binaire non facultatif a disparu à cause de ce bogue, je remplace awakeFromFetch dans ma sous-classe d'objet géré pour m'assurer que les attributs requis ne sont pas nuls, et s'ils le sont, je définissez-les sur une image d'espace réservé afin qu'ils puissent être enregistrés sans l'échec de la validation.

10
rodhan