¿Cómo lidiar con instancias temporales de NSManagedObject?

86

Necesito crear NSManagedObjectinstancias, hacer algunas cosas con ellas y luego tirarlas a la basura o almacenarlas en sqlite db. El problema es que no puedo crear instancias NSManagedObjectsin conexión a NSManagedObjectContexty esto significa que tengo que aclarar de alguna manera después de decidir que no necesito algunos de los objetos en mi base de datos.

Para lidiar con eso, he creado un almacén en memoria usando el mismo coordinador y estoy colocando objetos temporales allí usando assignObject:toPersistentStore.Ahora, ¿cómo me aseguro de que estos objetos temporales no lleguen a los datos, que obtengo del común al contexto de ambas tiendas? ¿O tengo que crear contextos separados para tal tarea?


UPD:

Ahora estoy pensando en crear un contexto separado para el almacenamiento en memoria. ¿Cómo muevo objetos de un contexto a otro? ¿Solo usa [insertObject de contexto:]? ¿Funcionará bien en esta configuración? Si inserto un objeto del gráfico de objetos, ¿el gráfico completo también se inserta en el contexto?

fspirit
fuente
Esta debería ser una pregunta separada ya que ha marcado esta como respondida. Cree una nueva pregunta y explique POR QUÉ siente que necesita una pila de datos centrales completa separada SOLO para un almacenamiento en memoria. Estaré encantado de explorar la cuestión contigo.
Marcus S. Zarra
La sección UPD ahora no es relevante, porque he elegido otro enfoque, vea mi último comentario a su respuesta.
fspirit

Respuestas:

146

NOTA: Esta respuesta es muy antigua. Consulte los comentarios para ver el historial completo. Desde entonces, mi recomendación ha cambiado y ya no recomiendo usar NSManagedObjectinstancias no asociadas . Mi recomendación actual es utilizar NSManagedObjectContextinstancias secundarias temporales .

Respuesta original

La forma más sencilla de hacer esto es crear sus NSManagedObjectinstancias sin un asociado NSManagedObjectContext.

NSEntityDescription *entity = [NSEntityDescription entityForName:@"MyEntity" inManagedObjectContext:myMOC];
NSManagedObject *unassociatedObject = [[NSManagedObject alloc] initWithEntity:entity insertIntoManagedObjectContext:nil];

Luego, cuando quieras guardarlo:

[myMOC insertObject:unassociatedObject];
NSError *error = nil;
if (![myMoc save:&error]) {
  //Respond to the error
}
Marcus S. Zarra
fuente
6
Si unssociatedObject tiene referencias a otros objetos no asociados, ¿debería insertarlos uno por uno o myMOC es lo suficientemente inteligente como para recopilar todas las referencias e insertarlas también?
fspirit
6
También es lo suficientemente inteligente para manejar las relaciones.
Marcus S. Zarra
2
Me gusta que este enfoque le permita tratar los MO como objetos de datos normales antes de decidir almacenarlos, pero me preocupa qué tan "respaldado" por el contrato de CoreData y, por lo tanto, qué tan preparado para el futuro. ¿Apple menciona o usa este enfoque en alguna parte? Porque si no, una futura versión de iOS podría cambiar las propiedades dinámicas para depender del MOC y romper este enfoque. Los documentos de Apple no son claros al respecto: enfatizan la importancia del contexto y el inicializador designado, pero hay una mención en el documento de MO que dice "si el contexto no es nulo, entonces ...", lo que sugiere que nulo podría estar bien
Ruibarbo
41
Usé este enfoque hace un tiempo, pero comencé a ver comportamientos extraños y fallas cuando modifiqué esos objetos y / o creé relaciones para ellos antes de insertarlos en un MOC. Hablé de esto con un ingeniero de Core Data en WWDC y dijo que, si bien la API para objetos no asociados está ahí, recomendó encarecidamente no usarla como un MOC que depende en gran medida de las notificaciones KVO enviadas por sus objetos. Sugirió usar NSObject regular para objetos temporales, ya que es mucho más seguro.
Adrian Schönig
7
Esto no parece funcionar bien con iOS 8, especialmente con relaciones persistentes. ¿Alguien más puede confirmar esto?
Janum Trivedi
40

iOS5 ofrece una alternativa más sencilla a la respuesta de Mike Weller. En su lugar, utilice un NSManagedObjectContext secundario . Elimina la necesidad de trampolín a través de NSNotificationCenter

Para crear un contexto secundario:

NSManagedObjectContext *childContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
childContext.parentContext = myMangedObjectContext;

Luego crea tus objetos usando el contexto secundario:

NSManagedObject *o = [NSEntityDescription insertNewObjectForEntityForName:@"MyObject" inManagedObjectContext:childContext];

Los cambios solo se aplican cuando se guarda el contexto secundario. Entonces, para descartar los cambios, simplemente no guarde.

Todavía hay una limitación en las relaciones. es decir, no puede crear relaciones con objetos en otros contextos. Para sortear esto, use objectID's, para obtener el objeto del contexto secundario. p.ej.

NSManagedObjectID *mid = [myManagedObject objectID];
MyManagedObject *mySafeManagedObject = [childContext objectWithID:mid];
object.relationship=mySafeManagedObject;

Tenga en cuenta que guardar el contexto secundario aplica los cambios al contexto principal. Guardar el contexto principal persiste los cambios.

Consulte la sesión 214 de la wwdc 2012 para obtener una explicación completa.

desfile de tren
fuente
1
¡Gracias por sugerir esto! Escribí una demostración probando este método en comparación con el uso de un contexto nulo y al menos en OSX, esto funcionó mientras que la inserción de un contexto nulo perdió sus atributos al guardar - demostración en github.com/seltzered/CoreDataMagicalRecordTempObjectsDemo
Vivek Gani
¿Cuál está mocen el tercer fragmento? Es childContexto myMangedObjectContext?
bugloaf
Es el childContext
railwayparade
esta solución es mejor que tener el contexto nulo.
Will Y
Dado que NSManagedObjectya proporciona lo relevante NSManagedObjectContext, puede automatizar la elección del contexto: NSManagedObject* objectRelatedContextually = [objectWithRelationship.managedObjectContext objectWithID:objectRelated.objectID];y luego objectWithRelationship.relationship = objectRelatedContextually;.
Gary
9

La forma correcta de lograr este tipo de cosas es con un nuevo contexto de objeto administrado. Creas un contexto de objeto administrado con el mismo almacén persistente:

NSManagedObjectContext *tempContext = [[[NSManagedObjectContext alloc] init] autorelease];
[tempContext setPersistentStore:[originalContext persistentStore]];

Luego agrega nuevos objetos, los muta, etc.

Cuando llega el momento de guardar, debe llamar a [tempContext save: ...] en tempContext, y manejar la notificación de guardar para fusionarla en su contexto original. Para descartar los objetos, simplemente suelte este contexto temporal y olvídese de él.

Entonces, cuando guarda el contexto temporal, los cambios se conservan en la tienda y solo necesita recuperar esos cambios en su contexto principal:

/* Called when the temp context is saved */
- (void)tempContextSaved:(NSNotification *)notification {
    /* Merge the changes into the original managed object context */
    [originalContext mergeChangesFromContextDidSaveNotification:notification];
}

// Here's where we do the save itself

// Add the notification handler
[[NSNotificationCenter defaultCenter] addObserver:self
                                         selector:@selector(tempContextSaved:)
                                             name:NSManagedObjectContextDidSaveNotification
                                           object:tempContext];

// Save
[tempContext save:NULL];
// Remove the handler again
[[NSNotificationCenter defaultCenter] removeObserver:self
                                                name:NSManagedObjectContextDidSaveNotification
                                              object:tempContext];

Esta es también la forma en que debe manejar las operaciones de datos centrales de subprocesos múltiples. Un contexto por hilo.

Si necesita acceder a objetos existentes desde este contexto temporal (para agregar relaciones, etc.), entonces debe usar la ID del objeto para obtener una nueva instancia como esta:

NSManagedObject *objectInOriginalContext = ...;
NSManagedObject *objectInTemporaryContext = [tempContext objectWithID:[objectInOriginalContext objectID]];

Si intenta utilizar un NSManagedObjecten el contexto incorrecto, obtendrá excepciones al guardar.

Mike Weller
fuente
Crear un segundo contexto solo para esto es un desperdicio, ya que estar parado NSManagedObjectContextes costoso tanto en memoria como en CPU. Me doy cuenta de que esto estaba originalmente en algunos de los ejemplos de Apple, pero han actualizado y corregido esos ejemplos.
Marcus S. Zarra
2
Apple todavía está usando esta técnica (creando un segundo contexto de objeto administrado) para el código de ejemplo de CoreDataBooks.
nevan king
1
Tenga en cuenta que Apple ha actualizado CoreDataBooks, de hecho, todavía usa dos contextos, pero ahora el segundo contexto es hijo del primero. Esta técnica se analiza (y recomienda) en la presentación 303 de la WWDC 2011 (novedades en Core Data en iOS) y se menciona aquí (con el código mucho, MUCHO más simple para fusionar cambios hacia arriba) stackoverflow.com/questions/9791469/…
Ruibarbo
4
"Crear un segundo contexto solo para esto es un desperdicio, ya que crear un NSManagedObjectContext es costoso tanto en memoria como en CPU". . No, no es. Las dependencias del coordinador de tienda persistente (modelo de objetos gestionados y tiendas concretas) son, no el contexto. Los contextos son ligeros.
sofocar el
3
@quellish De acuerdo. Apple ha declarado en sus recientes charlas sobre rendimiento de datos centrales en la WWDC que la creación de contextos es muy ligera.
Jesse
9

La creación de objetos temporales a partir de un contexto nulo funciona bien hasta que intentas tener una relación con un objeto cuyo contexto! = Nulo!

asegúrese de que está de acuerdo con eso.

usuario134611
fuente
No estoy de acuerdo con eso
Charlie
8

Lo que estás describiendo es exactamente para qué NSManagedObjectContextsirve.

De la guía de programación de datos básicos: conceptos básicos de datos básicos

Puede pensar en un contexto de objeto gestionado como un bloc de notas inteligente. Cuando recuperas objetos de una tienda persistente, traes copias temporales al bloc de notas donde forman un gráfico de objetos (o una colección de gráficos de objetos). A continuación, puede modificar esos objetos como desee. Sin embargo, a menos que realmente guarde esos cambios, el almacenamiento persistente permanece inalterado.

Y guía de programación de datos básicos: validación de objetos administrados

Esto también sustenta la idea de un contexto de objeto administrado que representa un "bloc de notas"; en general, puede traer objetos administrados al bloc de notas y editarlos como desee antes de finalmente realizar los cambios o descartarlos.

NSManagedObjectContextLos s están diseñados para ser livianos. Puede crearlos y descartarlos a voluntad: es el coordinador de tiendas persistentes y sus dependencias son "pesadas". Un único coordinador de tienda persistente puede tener muchos contextos asociados. Según el modelo de confinamiento de subprocesos obsoleto y antiguo, esto significaría establecer el mismo coordinador de tienda persistente en cada contexto. Hoy en día, significaría conectar contextos anidados a un contexto raíz que está asociado con el coordinador de tienda persistente.

Cree un contexto, cree y modifique objetos gestionados dentro de ese contexto. Si desea conservarlos y comunicar esos cambios, guarde el contexto. De lo contrario, deséchelo.

Intentar crear objetos gestionados independientemente de un NSManagedObjectContextes buscar problemas. Recuerde que Core Data es, en última instancia, un mecanismo de seguimiento de cambios para un gráfico de objetos. Debido a esto, los objetos gestionados son realmente parte del contexto del objeto gestionado . El contexto observa su ciclo de vida y, sin el contexto, no todas las funciones del objeto gestionado funcionarán correctamente.

sofocar
fuente
6

Dependiendo de su uso del objeto temporal, existen algunas advertencias a las recomendaciones anteriores. Mi caso de uso es que quiero crear un objeto temporal y vincularlo a las vistas. Cuando el usuario opta por guardar este objeto, quiero configurar relaciones con los objetos existentes y guardar. Quiero hacer esto para evitar crear un objeto temporal que contenga esos valores. (Sí, podría esperar hasta que el usuario guarde y luego tomar el contenido de la vista, pero estoy colocando estas vistas dentro de una tabla y la lógica para hacer esto es menos elegante).

Las opciones para objetos temporales son:

1) (Preferido) Cree el objeto temporal en un contexto secundario. Esto no funcionará porque estoy vinculando el objeto a la interfaz de usuario y no puedo garantizar que los descriptores de acceso al objeto se llamen en el contexto secundario. (No he encontrado documentación que indique lo contrario, así que debo asumir).

2) Cree el objeto temporal con un contexto de objeto nulo. Esto no funciona y da como resultado la pérdida / corrupción de datos.

Mi solución: Resolví esto creando el objeto temporal con un contexto de objeto nulo, pero cuando guardo el objeto, en lugar de insertarlo como # 2, copio todos sus atributos en un nuevo objeto que creo en el contexto principal. Creé un método de apoyo en mi subclase NSManagedObject llamado cloneInto: que me permite copiar atributos y relaciones fácilmente para cualquier objeto.

Greg
fuente
Eso es lo que estoy buscando. Pero mi duda es ¿cómo manejará los atributos de la relación?
Mani
1

Para mí, la respuesta de Marcus no funcionó. Esto es lo que funcionó para mí:

NSEntityDescription entityForName:@"MyEntity" inManagedObjectContext:myMOC];
NSManagedObject *unassociatedObject = [[NSManagedObject alloc] initWithEntity:entity insertIntoManagedObjectContext:nil];

entonces, si decido guardarlo:

[myMOC insertObject:unassociatedObjet];
NSError *error = nil;
[myMoc save:&error];
//Check the error!

Tampoco debemos olvidar liberarlo.

[unassociatedObject release]
Lucas
fuente
1

Estoy reescribiendo esta respuesta para Swift como todas las preguntas similares para una redirección rápida a esta pregunta.

Puede declarar el objeto sin ningún ManagedContext utilizando el siguiente código.

let entity = NSEntityDescription.entity(forEntityName: "EntityName", in: myContext)
let unassociatedObject = NSManagedObject.init(entity: entity!, insertInto: nil)

Posteriormente, para guardar el objeto puede insertarlo en el contexto y guardarlo.

myContext.insert(unassociatedObject)
// Saving the object
do {
    try self.stack.saveContext()
    } catch {
        print("save unsuccessful")
    }
}
Mitul Jindal
fuente