_PFFaultHandlerLookupRow crash while saving child context

Hola, mi aplicación se bloquea cuando se cambia el object gestionado en un context secundario (guardado después) y se elimina en otro context secundario (guardado primero).
Cómo reproducir:
1. Cree un nuevo proyecto con la plantilla 'Aplicación vacía' y los datos básicos habilitados.
2. Cambie el getObject managedContext a siguiente (he cambiado el tipo de concurrency)

 - (NSManagedObjectContext *) managedObjectContext
 {
     if (_managedObjectContext! = nil) {
         devolver _managedObjectContext;
     }

     NSPersistentStoreCoordinator * coordinator = [self persistentStoreCoordinator];
     si (coordinador! = nil) {
         _managedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType: NSMainQueueConcurrencyType];
         [_managedObjectContext setPersistentStoreCoordinator: coordinator];
     }
     devolver _managedObjectContext;
 }

3.Por favor, reemplace el método didfinishLaunching para seguir

 - (BOOL) aplicación: aplicación (UIApplication *) didFinishLaunchingWithOptions: (NSDictionary *) launchOptions
 {
     self.window = [[UIWindow alloc] initWithFrame: [[UIScreen mainScreen] bounds]];
     // Anular el punto para la personalización después del inicio de la aplicación.
     self.window.backgroundColor = [UIColor whiteColor];
     [self.window makeKeyAndVisible];

     {
         //insert
         NSManagedObjectContext * insertingContext = [[NSManagedObjectContext alloc] initWithConcurrencyType: NSPrivateQueueConcurrencyType];
         [insertingContext setParentContext: self.managedObjectContext];
         [insertingContext performBlockAndWait: ^ {
             Test * test = (Test *) [NSEntityDescription insertNewObjectForEntityForName: @ "Test" inManagedObjectContext: insertingContext];
             test.test = @ "testing";
             [insertandoContext save: nil];
             [self.managedObjectContext performBlockAndWait: ^ {
                 [self.managedObjectContext save: nil];
             }];
             NSLog (@ "insertado y guardado en la tienda de persistencia");
         }];
     }

     {
         // get el mes y cambiar la propiedad
         NSManagedObjectContext * acceesingContext = [[NSManagedObjectContext alloc] initWithConcurrencyType: NSPrivateQueueConcurrencyType];
         {
             [acceesingContext setParentContext: self.managedObjectContext];
             [acceesingContext performBlockAndWait: ^ {
                 NSFetchRequest * request = [[NSFetchRequest alloc] init];
                 [request setEntity: [NSEntityDescription entityForName: @ "Test" inManagedObjectContext: acceesingContext]];
                 NSArray * results = [acceesingContext executeFetchRequest: request error: nil];
                 if ([count de resultados]> 0)
                 {
                     Test * test = [results objectAtIndex: 0];
                     test.test = @ "hola";
                     NSLog (@ "accedió y cambió la propiedad para que se dispare el error");
                 }

             }];
         }
         {
             NSManagedObjectContext * deletingContext = [[NSManagedObjectContext alloc] initWithConcurrencyType: NSPrivateQueueConcurrencyType];
             [deletingContext setParentContext: self.managedObjectContext];
             [deletingContext performBlockAndWait: ^ {
                 NSFetchRequest * request = [[NSFetchRequest alloc] init];
                 [request setEntity: [NSEntityDescription entityForName: @ "Test" inManagedObjectContext: deletingContext]];
                 NSArray * results = [deletingContext executeFetchRequest: request error: nil];
                 if ([count de resultados]> 0)
                 {
                     Test * test = [results objectAtIndex: 0];
                     [deletingContext deleteObject: test];
                     [borrarContext save: nil];
                     [self.managedObjectContext performBlockAndWait: ^ {
                         [self.managedObjectContext save: nil];
                     }];
                     NSLog (@ "eliminado y guardado en la tienda de persistencia");
                 }
             }];
         }

         [acceesingContext performBlock: ^ {
             // se está estrellando aquí, por favor ayuda.
             [acceesingContext save: nil];
         }];
     }

     devolver SÍ;
 }

4. Finalmente, agregue la entidad denominada 'Prueba' con un atributo 'testing' (NSString) y ejecute la aplicación

El problema a lo que me enfrento es cuando un niño moc ha obtenido un object gestionado y ha cambiado una propiedad en él, mientras que otro niño elimina y guarda los cambios en el almacén persistente. el locking se produce al save el moc que ha modificado el mes.

Informe del crash

 *** Terminación de la aplicación debido a la exception no detectada 'NSObjectInaccessibleException', razón: 'CoreData no pudo cumplir un error para' 0xd0000000001c0000 ''
 *** Primera tirada de llamadas:
 (
     0 CoreFoundation 0x0000000101bf0795 __exceptionPreprocess + 165
     1 libobjc.A.dylib 0x0000000101953991 objc_exception_throw + 43
     2 CoreData 0x0000000100278a93 _PFFaultHandlerLookupRow + 1075
     3 CoreData 0x00000001003063a3 - [NSManagedObject (_NSInternalMethods) _updateFromRefreshSnapshot: includingTransients:] + 243
     4 CoreData 0x00000001002aa563 - [NSManagedObjectContext (_NestedContextSupport) _copyChildObject: toParentObject: fromChildContext:] + 771
     5 CoreData 0x00000001002aa01b - [NSManagedObjectContext (_NestedContextSupport) _parentProcessSaveRequest: inContext: error:] + 1019
     6 CoreData 0x0000000100310243 __82- [NSManagedObjectContext (_NestedContextSupport) executeRequest: withContext: error:] _ block_invoke + 563
     7 libdispatch.dylib 0x0000000101fc205a _dispatch_barrier_sync_f_slow_invoke + 45
     8 libdispatch.dylib 0x0000000101fd16fd _dispatch_client_callout + 8
     9 libdispatch.dylib 0x0000000101fc146c _dispatch_main_queue_callback_4CF + 354
     10 CoreFoundation 0x0000000101c4e729 __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__ + 9
     11 CoreFoundation 0x0000000101b9b9a4 __CFRunLoopRun + 1764
     12 CoreFoundation 0x0000000101b9aed3 CFRunLoopRunSpecific + 467
     13 GraphicsServices 0x0000000103b893a4 GSEventRunModal + 161
     14 UIKit 0x00000001005bba63 UIApplicationMain + 1010
     15 TestCrash 0x00000001000040a3 principal + 115
     16 libdyld.dylib 0x000000010227e7e1 inicio + 0
     17 ???  0x0000000000000001 0x0 + 1
 )
 libc ++ abi.dylib: terminando con una exception no detectada de tipo _NSCoreDataException

La key aquí no es esa línea en el seguimiento de stack, es el post de exception:

*** Terminating app due to uncaught exception 'NSObjectInaccessibleException', reason: 'CoreData could not fulfill a fault for '0xd0000000001c0000 '' 

Si Core Data se queja de que un object es inaccesible, eso significa que no pudo encontrar el object en la tienda persistente. Veamos por qué sucede esto en su código:

  1. Cree una instancia utilizando insertingContext y guarde los cambios allí y en el elemento primario. En este punto, su único object está en el file de almacenamiento persistente.
  2. acceesingContext esta instancia con acceesingContext y realiza cambios, pero no guarda los cambios . En este punto, acceesingContext tiene acceesingContext en la memory, no guardados, en el object que acaba de get.
  3. Obtiene el object utilizando deletingContext y lo elimina. Luego guardas los cambios en deletingContext y en el padre. En este punto, ha eliminado el object del file de almacenamiento persistente. Sin embargo , y esto es crucial, acceesingContext todavía tiene cambios no guardados para ese object.
  4. Guarde los cambios en acceesingContext . Dado que acceesingContext tiene cambios no guardados en el object que buscó, intenta actualizar ese object. Pero ese object no existe en la tienda persistente, porque lo eliminó. Dado que acceesingContext no puede actualizar un object inexistente, arroja una exception y la aplicación se bloquea.

Una cosa a tener en count al usar contexts de objects gestionados nesteds es que los cambios de guardado solo empujan los cambios en una dirección: hacia el padre. Si tiene dos contexts hermanos (en este caso acceesingContext y deletingContext ), save los cambios en un hijo no actualiza automáticamente el otro.

Dado que obviamente es un código de demostración (gracias, BTW, ¡es más fácil seguirlo!) Es difícil estar completamente seguro de lo que debería hacer en su código de aplicación real. Un enfoque típico es escuchar NSManagedObjectContextDidSaveNotification y luego usar mergeChangesFromContextDidSaveNotification: para aplicar cambios de un context a otro. De esa manera, cuando un context elimina un object, puede actualizar otros contexts para reflejar ese hecho. Esto permite mantener múltiples contexts de objects gestionados sincronizados entre sí.