ios - tutorial - Datos principales "La base de datos parece estar dañada"-¿Qué causa este error?
swift 4 core data tutorial (3)
En primer lugar, su contexto de interfaz de usuario no debe ser un contexto de cola privada. Para eso es el NSMainQueueConcurrencyType
.
Segundo, no verifique el error al guardar. Verifique el retorno BOOL
del -save:
Ese error puede tener basura incluso en un guardado exitoso.
En tercer lugar, ¿cómo se ven tus otras dos salvaciones? Si todos están en colas privadas y se guardan de forma sincronizada, es posible que te encuentres en una condición de carrera. C debería guardar sincrónicamente, B debería guardar sincrónicamente y luego A debería ser asincrónico.
Me estoy golpeando la cabeza contra la pared aquí, estoy utilizando Core Data para una base de datos SQLLite, y puedo guardar con éxito en la base de datos (he comprobado los contenidos en un navegador SQLLite sin conexión), pero después de guardar el La primera consulta que intento ejecutar vuelve con el error que se muestra a continuación, y no puedo encontrar ninguna información útil en Internet relacionada con este error específico:
Datos principales: error: -executeRequest: excepción encontrada = La base de datos parece estar dañada. (clave principal no válida) con userInfo = {NSFilePath = "/ Users / user / Library / Application Support / iPhone Simulator / 7.0.3 / Documents / db.sqlite";
La pregunta aquí es qué causa este error, ya que no puedo encontrar ninguna información relacionada con él.
Para un pequeño trasfondo, esta es mi configuración, supongamos que tengo buenas razones para el diseño que se ha realizado y no respondo "Cambie su diseño" a menos que pueda ver algo fundamentalmente roto con el patrón en sí.
Tengo 3 contextos de objeto administrado, todos ellos son NSPrivateQueueConcurrencyType, el primero (A) se adjunta al Coordinador de tienda permanente, el segundo (B) tiene un conjunto A como su contexto principal y el tercero (C) tiene un conjunto B como es contexto principal - Una cadena. La razón de esto es que C es un contexto grabable, obteniendo datos de una fuente de red y sincronizándolos y guardándolos, B es un contexto compartido por los elementos de la UI, y quiero que responda, finalmente A es un contexto de contexto diseñado para descargar cualquier retraso para guardar en el disco apagado Contexto B y C
PSC <- A <- B <- C
Si saco el último paso (Guardar A en PSC), la aplicación funciona bien, guardando todo en la memoria y consultando los contextos de la memoria. El bloqueo solo se produce después de agregar el paso de guardado nuevamente, y solo en la primera consulta ejecutada contra el DB después de ese guardado. Tanto mi save como mi ejecución fetch están incluidas en performBlock:
Aquí está el último guardado:
- (void)deepSave
{
// Save to the Save Context which happens in memory, so the actual write to disk operation occurs on background thread
// Expects to be called with performBlock
NSError *error = nil;
[super save:&error];
NSAssert(!error, error.localizedDescription);
// Trigger the save context to save to disk, operation will be queued and free up read only context
NSManagedObjectContext *saveContext = self.parentContext;
[saveContext performBlock:^{
NSError *error = nil;
[saveContext save:&error];
NSAssert(!error, error.localizedDescription);
}];
}
Y aquí está la pila de ejecución (en el hilo de cola NSManagedObjectContext)
#0 0x0079588a in objc_exception_throw ()
#1 0x079d98e7 in -[NSSQLiteConnection handleCorruptedDB:] ()
#2 0x078d9b8d in -[NSSQLiteConnection fetchResultSet:usingFetchPlan:] ()
#3 0x078e24a5 in newFetchedRowsForFetchPlan_MT ()
#4 0x078cd48e in -[NSSQLCore newRowsForFetchPlan:] ()
#5 0x078cca8d in -[NSSQLCore objectsForFetchRequest:inContext:] ()
#6 0x078cc53f in -[NSSQLCore executeRequest:withContext:error:] ()
#7 0x078cbf62 in -[NSPersistentStoreCoordinator executeRequest:withContext:error:] ()
#8 0x078c96c6 in -[NSManagedObjectContext executeFetchRequest:error:] ()
#9 0x0791e526 in -[NSManagedObjectContext(_NestedContextSupport) _parentObjectsForFetchRequest:inContext:error:] ()
#10 0x0799c1f4 in __82-[NSManagedObjectContext(_NestedContextSupport) executeRequest:withContext:error:]_block_invoke ()
#11 0x0791e321 in internalBlockToNSManagedObjectContextPerform ()
#12 0x013c34b0 in _dispatch_client_callout ()
#13 0x013b0778 in _dispatch_barrier_sync_f_invoke ()
#14 0x013b0422 in dispatch_barrier_sync_f ()
#15 0x0791e2a2 in _perform ()
#16 0x0791e14e in -[NSManagedObjectContext(_NestedContextSupport) executeRequest:withContext:error:] ()
#17 0x078c96c6 in -[NSManagedObjectContext executeFetchRequest:error:] ()
#18 0x0791e526 in -[NSManagedObjectContext(_NestedContextSupport) _parentObjectsForFetchRequest:inContext:error:] ()
#19 0x0799c1f4 in __82-[NSManagedObjectContext(_NestedContextSupport) executeRequest:withContext:error:]_block_invoke ()
#20 0x0791e321 in internalBlockToNSManagedObjectContextPerform ()
#21 0x013c34b0 in _dispatch_client_callout ()
#22 0x013b0778 in _dispatch_barrier_sync_f_invoke ()
#23 0x013b0422 in dispatch_barrier_sync_f ()
#24 0x0791e2a2 in _perform ()
#25 0x0791e14e in -[NSManagedObjectContext(_NestedContextSupport) executeRequest:withContext:error:] ()
#26 0x078c96c6 in -[NSManagedObjectContext executeFetchRequest:error:] ()
De acuerdo, lo he rastreado. Parece que hay algo roto en propertiesToFetch frente a NSManagedObject resultType (no se supone que se use, nuestro error) en esta configuración, frente a un contexto que tiene un contexto principal en lugar de un coordinador persistente. Esta prueba de unidad muestra que todo lo que tiene que hacer es establecer una propiedad para recuperar para obtener este error (mientras se realiza la consulta sin las propiedades para recuperar funciona correctamente). La solución aquí para nosotros era detener el uso incorrecto de propiedades para recuperar :)
- (void)testManagedObjectContextDefect
{
NSManagedObjectContext *contextA = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
contextA.persistentStoreCoordinator = sqllitePersistentStoreCoordinator;
NSManagedObjectContext *contextB = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
contextB.parentContext = contextA;
NSEntityDescription *entityDescription = [NSEntityDescription entityForName:@"GCSCObject" inManagedObjectContext:contextB];
[contextB performBlockAndWait:^{
GCSCObject *object = [[GCSCObject alloc] initWithEntity:entityDescription insertIntoManagedObjectContext:contextB];
object.serverID = @"1";
NSError *error = nil;
XCTAssert([contextB save:&error] && !error, @"Failed to save - %@",error); // B -> A save
}];
[contextA performBlock:^{
NSError *error = nil;
XCTAssert([contextA save:&error] && !error, @"Failed to save - %@",error); // A -> PSC, background save
}];
[contextB performBlockAndWait:^{
NSFetchRequest *request = [[NSFetchRequest alloc] initWithEntityName:@"GCSCObject"];
NSError *error = nil;
NSArray *results = [contextB executeFetchRequest:request error:&error];
XCTAssert(results.count == 1 && !error, @"Fetch failed to retrieve - %@ / %@",results,error);
GCSCObject *object = results[0];
XCTAssert([object.serverID isEqualToString:@"1"], @"Value retrieval failed");
// Everything passes up to here, so far so good!
request = [[NSFetchRequest alloc] initWithEntityName:@"GCSCObject"];
request.propertiesToFetch = @[@"serverID"]; // This is the culprit of the index crash
results = [contextB executeFetchRequest:request error:&error];
XCTAssert(!error, @"%@", error.localizedDescription); // !!! HERE we have a failure, assert: "Core Data: error: -executeRequest: encountered exception = The database appears corrupt. (invalid primary key) with userInfo = { NSFilePath = "/path/db.sqlite }";
}];
}
En este caso GCSCObject es una entidad regular, y serverID es uno de sus parámetros (no importa qué parámetro se usa, ni qué tipo es, lo intenté con multiple. Aquí está la descripción del parámetro serverID que utilicé para esto prueba:
El bloqueo ocurre ya sea que proporcionemos o no el contexto. Guardar (aunque hacerlo anularía el punto de tener una cola de fondo para guardar)
Me encantaría recibir comentarios sobre por qué este podría ser el caso, pero por ahora, no usar propiedades para recuperar permite que nuestra aplicación funcione sin problemas. Estoy contemplando la presentación de un error de Apple aquí.
Si experimenta este error al realizar una solicitud de recuperación que está recuperando algún resultado agregado (suma, máximo, mínimo, ...) asegúrese de establecer
fetchRequest.resultType = NSDictionaryResultType;