Repáralo ahora
- Suelta el archivo
- La reparación ocurre en local
- Descarga el resultado
El tipo de almacén persistente predeterminado y recomendado de Core Data es NSSQLiteStoreType: una base de datos SQLite 3 normal en disco, habitualmente llamada <AppName>.sqlite dentro del directorio Library de la app. Cuando no abre, el coordinador del almacén lanza un error NSCocoaErrorDomain y SQLite califica el archivo de malformado, pero una base de datos malformada rara vez está vacía: las filas siguen en sus páginas, detrás de un índice roto. Suelta el .sqlite arriba — y añade su archivo lateral -wal si lo tienes — y la herramienta lee el archivo en tu navegador, reconstruye el esquema y vuelve a insertar cada fila que aún puede decodificar en un almacén nuevo y abrible. No se instala nada y la base de datos nunca se sube.
Qué escribe realmente Core Data en el disco
Core Data es un framework de grafo de objetos y persistencia, no una base de datos — la base de datos es SQLite, y Core Data controla su disposición. Abre un almacén de Core Data en cualquier explorador de SQLite y no verás nombres de tabla amables; verás un esquema deformado que genera el framework. Cada entidad se convierte en una tabla llamada Z + el nombre de la entidad en mayúsculas, así que una entidad Person es la tabla ZPERSON. Cada fila lleva tres columnas de sistema: Z_PK (la clave primaria entera), Z_ENT (qué entidad/subclase es la fila) y Z_OPT (un contador de versión para el bloqueo optimista). Tus atributos también llevan prefijo — firstName pasa a ser ZFIRSTNAME — y una relación a-uno se guarda como una columna de clave externa que contiene el Z_PK de la fila de destino.
Junto a tus datos hay tres tablas de contabilidad. Z_PRIMARYKEY guarda una fila por entidad con las columnas Z_ENT, Z_NAME, Z_SUPER y Z_MAX — la clave primaria más alta entregada hasta ahora, que Core Data incrementa para asignar la siguiente inserción. Z_METADATA guarda Z_VERSION, Z_UUID y Z_PLIST, un blob de plist binario con los metadatos del almacén, incluidos NSStoreModelVersionHashes y el UUID del almacén. Z_MODELCACHE cachea el modelo compilado. Las fechas se guardan como un valor REAL de segundos desde la fecha de referencia de Cocoa, 2001-01-01 00:00:00 UTC — no la época Unix — así que una marca de tiempo en bruto de 0 es el 1 de enero de 2001.
Todo esto vive en un contenedor SQLite estándar: un array plano de páginas de tamaño fijo (4096 bytes por defecto), siendo los primeros 16 bytes del archivo la cadena mágica SQLite format 3\000. La página 1 contiene la cabecera del archivo y el esquema sqlite_master; cada tabla Z es un árbol-b cuyas páginas interiores apuntan hacia abajo a páginas hoja que contienen los registros reales. Rompe un puntero y SQLite rechaza todo el archivo — aunque las páginas hoja llenas de filas estén intactas.
Los archivos -wal y -shm: cuál contiene tus datos
Desde iOS 7 (y OS X 10.9 Mavericks) Core Data abre su almacén en modo WAL — registro de escritura anticipada (write-ahead logging) — por defecto. Por eso un único almacén son en realidad hasta tres archivos en disco: <AppName>.sqlite, <AppName>.sqlite-wal y <AppName>.sqlite-shm. Entender la diferencia decide si sobreviven tus datos más recientes.
El archivo -wal es el registro de escritura anticipada. Cuando tu app confirma una transacción, las páginas modificadas se añaden al -wal como tramas en lugar de escribirse directamente en el .sqlite principal; solo un checkpoint periódico las integra de vuelta en la base de datos. La consecuencia: si la app se cerró de golpe o se forzó su cierre antes de un checkpoint, las filas confirmadas más recientes pueden vivir solo en el -wal. Su disposición es precisa — una cabecera de 32 bytes (número mágico 0x377f0682 o 0x377f0683, una versión de formato, el tamaño de página, una secuencia de checkpoint, dos valores de sal y una suma de comprobación) seguida de tramas, cada una con una cabecera de trama de 24 bytes (número de página, el tamaño de la base de datos en páginas para una trama de confirmación o 0 en caso contrario, las dos sales y dos sumas de comprobación) más una página de datos.
El archivo -shm es el índice-wal de memoria compartida (wal-index): una estructura de búsqueda que SQLite usa para localizar páginas dentro del -wal rápidamente. Es datos totalmente derivados — no contiene ninguna de tus filas y SQLite lo reconstruye automáticamente a partir del -wal. Así que la regla es simple: trae el -wal, ignora el -shm. Suelta primero el .sqlite y luego añade el -wal en la ranura lateral opcional: sus tramas confirmadas se superponen antes del análisis, de modo que la recuperación refleja la última transacción confirmada en lugar del último checkpoint. Copiar solo el .sqlite de un dispositivo y dejar atrás el -wal es la forma clásica de perder datos recientes en silencio — una trampa tanto para la recuperación tras un fallo como para la extracción forense.
Los errores que ves y qué recupera el rescate de filas
En la capa de SQLite el almacén falla con uno de dos códigos de resultado. SQLITE_CORRUPT (código 11) imprime "database disk image is malformed": el motor siguió un puntero y encontró algo que no es una página de árbol-b válida — una página a ceros, un desplazamiento de celda incorrecto, un recuento de páginas que no concuerda con la longitud del archivo. SQLITE_NOTADB (código 26) imprime "file is not a database": los 16 bytes mágicos de la cabecera son incorrectos, lo que significa que o bien se dañaron los bytes de cabecera (las páginas de detrás suelen estar bien) o el archivo es realmente otra cosa o está cifrado. Core Data lo envuelve y presenta NSCocoaErrorDomain código 259 (NSFileReadCorruptFileError) — "The file couldn't be opened because it isn't in the correct format." — mientras que el código SQLite en bruto queda dentro del userInfo del error bajo la clave NSSQLiteErrorDomain, de modo que una línea de registro que diga NSSQLiteErrorDomain=11 es tu confirmación de que se trata de corrupción a nivel de página, no de una incompatibilidad de versión de esquema.
La recuperación no intenta arreglar el archivo roto sobre la marcha. Lee todo lo que sigue siendo decodificable y lo escribe en una base de datos nueva — la misma estrategia que el propio comando .recover de SQLite. Recorre el árbol-b de cada tabla Z y decodifica los registros de las hojas; para las páginas que la cadena de punteros ya no alcanza, recurre a un barrido de páginas en bruto, capturando registros autodescriptivos directamente del archivo. Las filas que no se pueden atribuir a una tabla conocida acaban en una tabla lost_and_found en lugar de descartarse, y los valores Z_MAX de Z_PRIMARYKEY se reconstruyen a partir de las filas recuperadas para que la app pueda seguir insertando sin colisiones de clave primaria. Como el barrido también lee páginas de la lista libre (freelist) y sin compactar, las filas que tu app borró antes pueden reaparecer — útil, pero conviene saberlo.
Los datos binarios externos viven fuera del almacén
Un límite honesto es específico de Core Data. Cuando un atributo binario (una foto, un PDF, audio) tiene marcado "Allows External Storage", Core Data decide valor por valor si incrusta los bytes en la fila o los escribe como un archivo aparte cuando superan unos 100 KB. Esos blobs externos se guardan bajo un directorio de soporte oculto junto al almacén — .<storename>_SUPPORT/_EXTERNAL_DATA/ — con nombres de archivo UUID, y la fila de la base de datos conserva solo una referencia a ese archivo, no los bytes.
Así que recuperar el .sqlite por sí solo devuelve cada atributo escalar y cada blob incrustado, pero para los datos almacenados externamente devuelve la referencia, no la imagen. Si además tienes la carpeta _EXTERNAL_DATA del mismo contenedor de la app, esos archivos se emparejan de nuevo por nombre; si ese directorio ha desaparecido, ninguna recuperación de base de datos puede reconstruir bytes que nunca estuvieron dentro de la base de datos. Y como un almacén de Core Data puede contener todo el estado privado de una app — cada nota, mensaje, registro de salud o cuenta que un usuario haya guardado — todo esto se ejecuta en tu navegador: el archivo se lee del disco, se reconstruye en la pestaña y puedes ver en la pestaña Red que no sale ni un byte de tu equipo.
Qué puede y qué no puede reparar
Puede reparar
- "database disk image is malformed" (SQLITE_CORRUPT / código 11) por una página de árbol-b dañada o un puntero de celda incorrecto, con las páginas ZENTITY de detrás intactas
- Filas de cada tabla ZENTITY que aún se decodifican — se extraen y se vuelven a insertar en un almacén .sqlite nuevo y abrible
- Cambios confirmados sin checkpoint superpuestos desde un archivo lateral -wal aportado (las filas más recientes tras un fallo o un cierre forzado)
- Un almacén cuya cabecera de 16 bytes o cuyo esquema está dañado pero cuyas páginas hoja sobreviven — el esquema se reconstruye a partir de las páginas
- Filas que los punteros del árbol-b ya no alcanzan, recuperadas por un barrido de páginas en bruto hacia una tabla lost_and_found, con Z_PRIMARYKEY.Z_MAX reconstruido
No puede reparar
- Filas sobrescritas físicamente o truncadas del final del archivo — esos bytes ya no existen en el disco
- Blobs binarios externos guardados en .<storename>_SUPPORT/_EXTERNAL_DATA cuando no tienes también esos archivos — la fila solo contiene una referencia
- Almacenes cifrados (datos con NSFileProtectionComplete copiados con el dispositivo bloqueado, o SQLCipher) sin la clave — las páginas se leen como texto cifrado
- El archivo -shm por sí solo: es un índice-wal reconstruible, no tus datos — el -wal es el archivo que lleva las confirmaciones recientes
- Una garantía de que los datos recuperados estén completos o sean correctos — los datos de un almacén dañado siempre son sospechosos; verifícalos contra una fuente conocida y fiable
Si una reparación falla, te decimos por qué (datos que faltan frente a estructura dañada), y nunca se te cobra por una reparación fallida.