Réparez un magasin Core Data iOS corrompu

Sous l'API Swift et Objective-C, Core Data écrit une base de données SQLite 3 ordinaire. Quand l'app signale que le magasin "n'a pas le bon format", cela veut généralement dire qu'une seule page ou qu'un seul pointeur s'est cassé — les lignes que votre app a enregistrées sont toujours dans le fichier. Les récupérer consiste à lire le fichier page par page, pas à confier les données de vos utilisateurs à un serveur.

Votre fichier ne quitte jamais votre appareil — la réparation s'exécute dans votre navigateur. 0 octet téléversé.

Your data is the big block — usually intact. What breaks is the small index. Repair rebuilds it.

Drop a file here or browseTap to choose a file

ZIP · Office · PDF · video · JPG · PNG · RAR/7z · SQLite — repaired right here in your browser. Nothing is uploaded

0 bytes uploadedFree: 3 repairs/day — up to 500 MB video, 100 MB docs & archives, 50 MB photos. Free download. Single repair €5.90. No account required.

Réparer maintenant

  1. Déposez le fichier
  2. La réparation s'exécute en local
  3. Téléchargez le résultat

Le type de magasin persistant par défaut et recommandé de Core Data est NSSQLiteStoreType : une base de données SQLite 3 normale sur le disque, généralement nommée <AppName>.sqlite dans le répertoire Library de l'app. Quand il ne s'ouvre pas, le coordinateur du magasin lève une erreur NSCocoaErrorDomain et SQLite qualifie le fichier de malformé, mais une base de données malformée est rarement vide : les lignes sont toujours dans leurs pages, derrière un index cassé. Déposez le .sqlite ci-dessus — et ajoutez son fichier annexe -wal si vous l'avez — et l'outil lit le fichier dans votre navigateur, reconstruit le schéma et réinsère chaque ligne qu'il parvient encore à décoder dans un magasin neuf et ouvrable. Rien n'est installé et la base de données n'est jamais envoyée.

Ce que Core Data écrit réellement sur le disque

Core Data est un framework de graphe d'objets et de persistance, pas une base de données — la base de données, c'est SQLite, et Core Data en maîtrise l'agencement. Ouvrez un magasin Core Data dans n'importe quel explorateur SQLite et vous n'y verrez pas des noms de table conviviaux ; vous verrez un schéma déformé généré par le framework. Chaque entité devient une table nommée Z + le nom de l'entité en majuscules, si bien qu'une entité Person devient la table ZPERSON. Chaque ligne porte trois colonnes système : Z_PK (la clé primaire entière), Z_ENT (quelle entité/sous-classe est la ligne) et Z_OPT (un compteur de version pour le verrouillage optimiste). Vos attributs portent eux aussi un préfixe — firstName devient ZFIRSTNAME — et une relation à-un est stockée comme une colonne de clé étrangère contenant le Z_PK de la ligne de destination.

À côté de vos données se trouvent trois tables de gestion interne. Z_PRIMARYKEY conserve une ligne par entité avec les colonnes Z_ENT, Z_NAME, Z_SUPER et Z_MAX — la clé primaire la plus élevée attribuée jusqu'à présent, que Core Data incrémente pour allouer l'insertion suivante. Z_METADATA conserve Z_VERSION, Z_UUID et Z_PLIST, un blob plist binaire contenant les métadonnées du magasin, dont NSStoreModelVersionHashes et l'UUID du magasin. Z_MODELCACHE met en cache le modèle compilé. Les dates sont stockées comme une valeur REAL en secondes depuis la date de référence de Cocoa, 2001-01-01 00:00:00 UTC — et non l'époque Unix — de sorte qu'un horodatage brut de 0 correspond au 1er janvier 2001.

Tout cela réside dans un conteneur SQLite standard : un tableau plat de pages de taille fixe (4096 octets par défaut), les 16 premiers octets du fichier étant la chaîne magique SQLite format 3\000. La page 1 contient l'en-tête du fichier et le schéma sqlite_master ; chaque table Z est un arbre-b dont les pages internes pointent vers le bas, vers des pages feuilles contenant les enregistrements réels. Cassez un pointeur et SQLite rejette tout le fichier — même si les pages feuilles remplies de lignes sont intactes.

Les fichiers -wal et -shm : lequel contient vos données

Depuis iOS 7 (et OS X 10.9 Mavericks), Core Data ouvre son magasin en mode WAL — journalisation en écriture anticipée (write-ahead logging) — par défaut. C'est pourquoi un seul magasin correspond en réalité à un maximum de trois fichiers sur le disque : <AppName>.sqlite, <AppName>.sqlite-wal et <AppName>.sqlite-shm. Comprendre la différence détermine si vos données les plus récentes survivent.

Le fichier -wal est le journal d'écriture anticipée. Quand votre app valide une transaction, les pages modifiées sont ajoutées au -wal sous forme de trames plutôt que d'être écrites directement dans le .sqlite principal ; seul un checkpoint périodique les réintègre dans la base de données. La conséquence : si l'app s'est fermée brutalement ou a été forcée à quitter avant un checkpoint, les lignes validées les plus récentes peuvent vivre uniquement dans le -wal. Son agencement est précis — un en-tête de 32 octets (nombre magique 0x377f0682 ou 0x377f0683, une version de format, la taille de page, une séquence de checkpoint, deux valeurs de sel et une somme de contrôle) suivi de trames, chacune avec un en-tête de trame de 24 octets (numéro de page, la taille de la base de données en pages pour une trame de validation ou 0 sinon, les deux sels et deux sommes de contrôle) plus une page de données.

Le fichier -shm est l'index-wal en mémoire partagée (wal-index) : une structure de recherche que SQLite utilise pour localiser rapidement les pages à l'intérieur du -wal. Ce sont des données entièrement dérivées — il ne contient aucune de vos lignes et SQLite le reconstruit automatiquement à partir du -wal. La règle est donc simple : apportez le -wal, ignorez le -shm. Déposez d'abord le .sqlite, puis ajoutez le -wal dans l'emplacement annexe facultatif : ses trames validées sont superposées avant l'analyse, de sorte que la récupération reflète la dernière transaction validée plutôt que le dernier checkpoint. Copier uniquement le .sqlite depuis un appareil et laisser le -wal derrière soi est la manière classique de perdre des données récentes en silence — un piège aussi bien pour la récupération après un plantage que pour l'extraction forensique.

Les erreurs que vous voyez, et ce que récupère le sauvetage des lignes

Au niveau de SQLite, le magasin échoue avec l'un de deux codes de résultat. SQLITE_CORRUPT (code 11) affiche "database disk image is malformed" : le moteur a suivi un pointeur et a trouvé quelque chose qui n'est pas une page d'arbre-b valide — une page mise à zéro, un décalage de cellule incorrect, un nombre de pages qui ne concorde pas avec la longueur du fichier. SQLITE_NOTADB (code 26) affiche "file is not a database" : les 16 octets magiques de l'en-tête sont incorrects, ce qui signifie soit que les octets d'en-tête ont été endommagés (les pages situées derrière sont souvent intactes), soit que le fichier est réellement autre chose ou qu'il est chiffré. Core Data l'enveloppe et présente NSCocoaErrorDomain code 259 (NSFileReadCorruptFileError) — "The file couldn't be opened because it isn't in the correct format." — tandis que le code SQLite brut reste à l'intérieur du userInfo de l'erreur, sous la clé NSSQLiteErrorDomain, de sorte qu'une ligne de journal indiquant NSSQLiteErrorDomain=11 est votre confirmation qu'il s'agit d'une corruption au niveau de la page, et non d'une incompatibilité de version de schéma.

La récupération n'essaie pas de réparer le fichier cassé sur place. Elle lit tout ce qui reste décodable et l'écrit dans une base de données neuve — la même stratégie que la commande .recover de SQLite elle-même. Elle parcourt l'arbre-b de chaque table Z et décode les enregistrements des feuilles ; pour les pages que la chaîne de pointeurs n'atteint plus, elle se rabat sur un balayage de pages brutes, capturant les enregistrements auto-descriptifs directement dans le fichier. Les lignes qui ne peuvent pas être rattachées à une table connue atterrissent dans une table lost_and_found au lieu d'être jetées, et les valeurs Z_MAX de Z_PRIMARYKEY sont reconstruites à partir des lignes récupérées afin que l'app puisse continuer à insérer sans collisions de clé primaire. Comme le balayage lit aussi les pages de la liste libre (freelist) et non compactées, les lignes que votre app avait supprimées auparavant peuvent réapparaître — utile, mais bon à savoir.

Les données binaires externes vivent en dehors du magasin

Une limite honnête est propre à Core Data. Quand un attribut binaire (une photo, un PDF, de l'audio) a l'option "Allows External Storage" cochée, Core Data décide valeur par valeur s'il intègre les octets dans la ligne ou les écrit comme un fichier séparé lorsqu'ils dépassent environ 100 Ko. Ces blobs externes sont stockés dans un répertoire de support caché à côté du magasin — .<storename>_SUPPORT/_EXTERNAL_DATA/ — avec des noms de fichier UUID, et la ligne de la base de données ne conserve qu'une référence vers ce fichier, et non les octets.

Ainsi, récupérer le .sqlite seul renvoie chaque attribut scalaire et chaque blob intégré, mais pour les données stockées en externe, il renvoie la référence, pas l'image. Si vous avez en plus le dossier _EXTERNAL_DATA du même conteneur d'app, ces fichiers se réapparient par nom ; si ce répertoire a disparu, aucune récupération de base de données ne peut reconstruire des octets qui n'ont jamais été à l'intérieur de la base de données. Et comme un magasin Core Data peut contenir tout l'état privé d'une app — chaque note, message, donnée de santé ou compte qu'un utilisateur a un jour enregistré — tout ceci s'exécute dans votre navigateur : le fichier est lu depuis le disque, reconstruit dans l'onglet, et vous pouvez vérifier dans l'onglet Réseau qu'aucun octet ne quitte votre machine.

Ce que cela peut et ne peut pas réparer

Peut réparer

  • "database disk image is malformed" (SQLITE_CORRUPT / code 11) dû à une page d'arbre-b endommagée ou à un pointeur de cellule incorrect, avec les pages ZENTITY situées derrière intactes
  • Les lignes de chaque table ZENTITY encore décodables — extraites puis réinsérées dans un magasin .sqlite neuf et ouvrable
  • Les modifications validées sans checkpoint, superposées depuis un fichier annexe -wal fourni (les lignes les plus récentes après un plantage ou une fermeture forcée)
  • Un magasin dont l'en-tête de 16 octets ou le schéma est endommagé mais dont les pages feuilles survivent — le schéma est reconstruit à partir des pages
  • Les lignes que les pointeurs de l'arbre-b n'atteignent plus, récupérées par un balayage de pages brutes vers une table lost_and_found, avec Z_PRIMARYKEY.Z_MAX reconstruit

Ne peut pas réparer

  • Les lignes physiquement écrasées ou tronquées à la fin du fichier — ces octets n'existent plus sur le disque
  • Les blobs binaires externes stockés dans .<storename>_SUPPORT/_EXTERNAL_DATA quand vous n'avez pas aussi ces fichiers — la ligne ne contient qu'une référence
  • Les magasins chiffrés (données avec NSFileProtectionComplete copiées alors que l'appareil était verrouillé, ou SQLCipher) sans la clé — les pages se lisent comme du texte chiffré
  • Le fichier -shm à lui seul : c'est un index-wal reconstructible, pas vos données — le -wal est le fichier qui porte les validations récentes
  • Une garantie que les données récupérées soient complètes ou correctes — les données d'un magasin endommagé sont toujours suspectes ; vérifiez-les par rapport à une source connue et fiable

Si une réparation échoue, nous vous expliquons pourquoi (données manquantes ou structure endommagée), et vous n'êtes jamais facturé pour une réparation échouée.

Questions fréquentes

Mon app enregistre "The file couldn't be opened because it isn't in the correct format." Mes données sont-elles perdues ?

En général non. C'est NSCocoaErrorDomain code 259 (NSFileReadCorruptFileError), et le code SQLite brut présent dans userInfo sous NSSQLiteErrorDomain est presque toujours 11 — SQLITE_CORRUPT, "database disk image is malformed." Cela signifie qu'une page ou un pointeur s'est cassé et que SQLite rejette tout le fichier ; les pages feuilles qui contiennent vos lignes sont généralement intactes et peuvent être extraites vers un magasin neuf.

Ai-je besoin des fichiers -wal et -shm, ou seulement du .sqlite ?

Apportez le .sqlite et, si vous l'avez, le -wal. Après un plantage, les lignes validées les plus récentes peuvent vivre uniquement dans le -wal jusqu'à ce qu'un checkpoint soit appliqué, donc l'ajouter comme fichier annexe récupère la dernière transaction. Le -shm est un index en mémoire partagée reconstructible qui ne contient aucune de vos données — vous pouvez l'ignorer. Copier uniquement le .sqlite et laisser le -wal derrière soi est la façon la plus courante de perdre des données récentes.

Pourquoi les tables s'appellent-elles ZPERSON, Z_PK, Z_METADATA, etc. ?

C'est le schéma déformé propre à Core Data. Chaque entité devient une table Z+ENTITÉ, chaque ligne a les colonnes système Z_PK, Z_ENT et Z_OPT, les attributs portent le préfixe Z, et la gestion interne réside dans Z_PRIMARYKEY, Z_METADATA et Z_MODELCACHE. La récupération conserve ces noms et reconstruit les compteurs Z_MAX de Z_PRIMARYKEY afin que le magasin récupéré se rouvre dans votre app et puisse encore insérer de nouvelles lignes.

Mes photos et pièces jointes reviendront-elles ?

Uniquement si elles étaient enregistrées à l'intérieur de la base de données. Un attribut binaire avec "Allows External Storage" activé écrit les valeurs de plus d'environ 100 Ko dans des fichiers séparés sous un répertoire caché .<storename>_SUPPORT/_EXTERNAL_DATA, ne conservant qu'une référence dans la ligne. Récupérer le .sqlite renvoie cette référence ; les octets ne reviennent que si vous avez aussi le dossier _EXTERNAL_DATA du même conteneur d'app.

Est-il prudent de faire passer la base de données de mon app par un outil de réparation en ligne ?

Un magasin Core Data est l'un des pires fichiers à confier à un serveur que vous ne contrôlez pas — il peut contenir chaque enregistrement, message ou identifiant que votre app a un jour enregistré pour un utilisateur. Ici, rien n'est envoyé : le fichier est lu depuis votre disque et reconstruit dans votre navigateur, et vous pouvez ouvrir l'onglet Réseau et confirmer qu'aucun octet de la base de données n'est transmis.

Associés : Réparer une base de données SQLite · Réparer des fichiers Excel · Vérifier la promesse du zéro envoi