ios - Implementación correcta de NSManagedObjectContext padre/hijo
objective-c cocoa (3)
Mi aplicación a veces inserta objetos en el contexto del objeto administrado que no están destinados a ser necesariamente guardados. Por ejemplo, cuando abro un modal ''agregar entidad'', creo un objeto administrado y lo asigno al modal. Si el usuario guarda desde ese modal, guardo el contexto. Si cancela, borro el objeto y no es necesario guardarlo.
Ahora introduje una función de ''importación'' que cambia a mi aplicación (usando un esquema de URL) y agrega una entidad. Debido a que uno de estos modales podría estar abierto, no es seguro guardar el contexto en este punto. El objeto transitorio creado para el modal se guardará, incluso si el usuario cancela, y no hay garantía de que la eliminación (de la operación de cancelación) se guarde más tarde: el usuario podría salir de la aplicación.
Del mismo modo, no puedo simplemente guardar cada vez que se cierra mi aplicación. Si el modal está abierto en ese punto, el objeto temporal se guardará incorrectamente.
Para abordar esto, intento utilizar un contexto infantil, como se discute here . Después de leer todo lo que pude encontrar en SO, aún tengo algunas preguntas:
¿Qué tipo de concurrencia debo usar para cada contexto? Recuerde que no estoy haciendo esto para los beneficios de rendimiento / enhebrado. Sé que no puedo usar NSConfinementConcurrencyType para el contexto principal si va a tener contextos secundarios, pero no estoy seguro de cuál de las otras dos opciones es la más adecuada. Para el contexto del niño, ¿tiene que coincidir? ¿O puedo usar el tipo de confinamiento aquí? Intenté una variedad de combinaciones y todas parecen funcionar bien, pero me gustaría saber cuál es la adecuada para mis requisitos.
(problema lateral) ¿Por qué solo puedo hacer que funcione si uso una clase iVar? Pensé que debería ser capaz de declarar el contexto temporal en el método donde se creó, y luego referirme a él usando entity.managedObjectContext. Pero parece ser nulo en el momento en que accedo a él? Esto se rectifica si en su lugar uso un iVar para mantener la referencia.
¿Cuál es la forma correcta de propagar el cambio al contexto principal? He visto varios comentarios usando diferentes implementaciones empaquetadas en bloque en cada uno de los contextos. ¿Depende de mi tipo de simultaneidad? Mi versión actual es:
//save the new entity in the temporary context NSError *error = nil; if (![myObject.managedObjectContext save:&error]) {NSLog(@"Error - unable to save new object in its (temporary) context");} //propogate the save to the main context [self.mainContext performBlock:^{ NSError *error2 = nil; if (![self.mainContext save:&error2]) {NSLog(@"Error - unable to merge new entity into main context");} }];
Cuando mi usuario guarda, envía un mensaje a su delegado (mi controlador de vista principal). El delegado pasa el objeto que se agregó y debe ubicar el mismo objeto en el contexto principal. Pero cuando lo busco en el contexto principal, no se encuentra. El contexto principal contiene la entidad, puedo registrar sus detalles y confirmar que está allí, pero la dirección es diferente. Si esto debe suceder (¿por qué?), ¿Cómo puedo ubicar el objeto agregado en el contexto principal después de guardarlo?
Gracias por cualquier idea. Perdón por una pregunta larga y de varias partes, pero pensé que era probable que alguien abordara todos estos problemas anteriormente.
Si usa el patrón principal / secundario, generalmente declara el contexto principal con
NSMainQueueConcurrencyType
y los contextos secundarios conNSPrivateQueueConcurrencyType
.NSConfinementConcurrencyType
se utiliza para el patrón de subprocesamiento clásico.Si desea mantener el contexto, de alguna manera necesita una referencia fuerte.
Simplemente llame al método save en el contexto secundario para insertar los cambios en el contexto principal, si desea conservar los datos, llame también a guardar en el contexto primario. No necesitas hacer esto dentro de un bloque.
Hay varios métodos para obtener un objeto específico de un contexto. No puedo decirte cuál funcionará en tu caso, pruébalos:
- objectRegisteredForID:
- objectWithID:
- existingObjectWithID:error:
El modelo MOC padre / hijo es una característica realmente poderosa de Core Data. Simplifica increíblemente el antiguo problema de simultaneidad con el que solíamos tener que lidiar. Sin embargo, como ha declarado, la concurrencia no es su problema. Para responder tu pregunta:
- Tradicionalmente, utiliza el
NSMainQueueConcurrencyType
para elNSManagedObjectContext
asociado con el hilo principal yNSPrivateQueueConcurrencyType
para contextos secundarios. El contexto hijo no necesita coincidir con su padre. ElNSConfinementConcurrencyType
es el que todos losNSManagedObjectContext
obtienen de forma predeterminada si no especifica un tipo. Básicamente es del tipo "Gestionaré mis propios hilos para datos básicos". - Sin ver su código, mi suposición sería que el alcance dentro del cual crea el contexto secundario termina y se limpia.
- Al utilizar el patrón de contexto principal / secundario, debe utilizar los métodos de bloqueo. El mayor beneficio de usar los métodos de bloque es que el sistema operativo manejará el envío de las llamadas a los métodos a los hilos correctos. Puede usar
performBlock
para la ejecución asincrónica operformBlockAndWait
para la ejecución síncrona.
Lo usarías como:
- (void)saveContexts {
[childContext performBlock:^{
NSError *childError = nil;
if ([childContext save:&childError]) {
[parentContext performBlock:^{
NSError *parentError = nil;
if (![parentContext save:&parentError]) {
NSLog(@"Error saving parent");
}
}];
} else {
NSLog(@"Error saving child");
}
}];
}
Ahora, debe tener en cuenta que los cambios realizados en el contexto secundario (por ejemplo, las entidades insertadas) no estarán disponibles para el contexto principal hasta que los guarde. Para el contexto hijo, el contexto principal es la tienda persistente. Cuando guardas, pasas esos cambios al padre, que luego puede guardarlos en la tienda persistente real. Guarda los cambios propuestos en un nivel. Por otro lado, ir a un contexto secundario hará que los datos bajen en todos los niveles (a través del padre y dentro del niño)
- Necesita usar alguna forma de
objectWithID
en managedObjectContext. Son la forma más segura (y realmente única) de pasar objetos entre contextos. Como Tom Harrington mencionó en los comentarios, es posible que desee utilizarexistingObjectWithID:error:
though porqueobjectWithID:
siempre devuelve un objeto, incluso si pasa una identificación no válida (que puede dar lugar a excepciones). Para más detalles: Link
He tenido problemas similares y aquí hay respuestas a algunas partes de sus preguntas: 1. Debería poder utilizar el tipo de concurrencia NSPrivateQueueConcurrencyType
o NSMainQueueConcurrencyType
2. Digamos que ha creado un contexto temporal tempContext
con el contexto principal mainContext
(esto está asumiendo iOS5). En ese caso, puede mover su objeto gestionado de tempContext
a mainContext
by-
object = (Object *)[mainContext objectWithID:object.objectID];
A continuación, puede guardar el mainContext en sí mismo.
Quizás también,
[childContext reset];
si desea restablecer el contexto temporal.