Come gestire le istanze temporanee di NSManagedObject?


86

Ho bisogno di creare NSManagedObjectistanze, fare alcune cose con esse e poi eliminarle o archiviarle su sqlite db. Il problema è che non posso creare istanze di NSManagedObjectnon connesso a NSManagedObjectContexte questo significa che devo chiarire in qualche modo dopo aver deciso che non ho bisogno di alcuni degli oggetti nel mio db.

Per affrontarlo, ho creato un archivio in memoria utilizzando lo stesso coordinatore e sto inserendo oggetti temporanei lì usando assignObject:toPersistentStore.Ora, come posso assicurarmi che questi oggetti temporanei non arrivino ai dati, che prendo dal comune a entrambi i negozi contesto? Oppure devo creare contesti separati per un'attività del genere?


UPD:

Ora sto pensando di creare un contesto separato per l'archivio in memoria. Come sposto gli oggetti da un contesto a un altro? Basta usare [context insertObject:]? Funzionerà bene in questa configurazione? Se inserisco un oggetto dal grafico degli oggetti, anche l'intero grafico viene inserito nel contesto?


Questa dovrebbe essere una domanda separata poiché l'hai contrassegnata come risposta. Crea una nuova domanda e spiega PERCHÉ ritieni di aver bisogno di un intero stack Core Data separato SOLO per un archivio in memoria. Sarò felice di esplorare la questione con te.
Marcus S. Zarra

La sezione UPD ora non è rilevante, perché ho scelto un altro approccio, vedi il mio ultimo commento alla tua risposta.
fspirit

Risposte:


146

NOTA: questa risposta è molto vecchia. Vedere i commenti per la cronologia completa. Da allora la mia raccomandazione è cambiata e non consiglio più di utilizzare NSManagedObjectistanze non associate . La mia raccomandazione attuale è di utilizzare NSManagedObjectContextistanze figlio temporanee .

Risposta originale

Il modo più semplice per farlo è creare le NSManagedObjectistanze senza un file NSManagedObjectContext.

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

Quindi, quando vuoi salvarlo:

[myMOC insertObject:unassociatedObject];
NSError *error = nil;
if (![myMoc save:&error]) {
  //Respond to the error
}

6
Se unassociatedObject ha riferimenti ad altri oggetti non associati, devo inserirli uno per uno o myMOC è abbastanza intelligente da raccogliere tutti i riferimenti e inserirli anche?
fspirit

6
È abbastanza intelligente da gestire anche le relazioni.
Marcus S. Zarra

2
Mi piace che questo approccio consenta di trattare gli MO come normali oggetti dati prima di decidere di memorizzarli, ma sono preoccupato per quanto "supportato" dal contratto CoreData e quindi per quanto sia a prova di futuro. Apple menziona o usa questo approccio da qualche parte? Perché in caso contrario, una futura versione di iOS potrebbe modificare le proprietà dinamiche per dipendere dal MOC e interrompere questo approccio. I documenti Apple non sono chiari su questo: sottolineano l'importanza del contesto e dell'inizializzatore designato, ma c'è una menzione nel documento MO che dice "se il contesto non è nullo, allora ..." suggerendo che nil potrebbe essere ok
Rabarbaro

41
Ho usato questo approccio qualche tempo fa, ma ho iniziato a vedere strani comportamenti e arresti anomali quando ho modificato quegli oggetti e / o creato relazioni per loro prima di inserirli in un MOC. Ne ho parlato con un ingegnere di Core Data al WWDC e ha detto che sebbene l'API per gli oggetti non associati sia presente, ha fortemente sconsigliato di utilizzarla come MOC si basa fortemente sulle notifiche KVO inviate dai suoi oggetti. Ha suggerito di utilizzare NSObject regolare per oggetti temporanei in quanto è molto più sicuro.
Adrian Schönig,

7
Questo non sembra funzionare bene con iOS 8, specialmente con relazioni persistenti. Qualcun altro può confermarlo?
Janum Trivedi

40

iOS5 fornisce un'alternativa più semplice alla risposta di Mike Weller. Utilizza invece un figlio NSManagedObjectContext. Elimina la necessità di trampolino attraverso NSNotificationCenter

Per creare un contesto figlio:

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

Quindi crea i tuoi oggetti usando il contesto figlio:

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

Le modifiche vengono applicate solo quando viene salvato il contesto figlio. Quindi per scartare le modifiche basta non salvare.

C'è ancora un limite alle relazioni. cioè non puoi creare relazioni con oggetti in altri contesti. Per aggirare questo problema, usa l'ID oggetto, per ottenere l'oggetto dal contesto figlio. per esempio.

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

Nota, il salvataggio del contesto figlio applica le modifiche al contesto padre. Il salvataggio del contesto genitore mantiene le modifiche.

Vedere la sessione 214 del wwdc 2012 per una spiegazione completa.


1
Grazie per aver suggerito questo! Ho scritto una demo testando questo metodo rispetto all'utilizzo di un contesto nullo e almeno su OSX, questo ha funzionato durante l'inserimento di un contesto nullo ha perso i suoi attributi durante il salvataggio - demo su github.com/seltzered/CoreDataMagicalRecordTempObjectsDemo
Vivek Gani

Quale è mocnel terzo frammento? È childContexto myMangedObjectContext?
bugloaf

È il ChildContext
Railwayparade

questa soluzione è migliore che avere il contesto nullo.
Will Y

Dato che NSManagedObjectfornisce già il relativo NSManagedObjectContext, puoi automatizzare la scelta del contesto: NSManagedObject* objectRelatedContextually = [objectWithRelationship.managedObjectContext objectWithID:objectRelated.objectID];e poi objectWithRelationship.relationship = objectRelatedContextually;.
Gary

9

Il modo corretto per ottenere questo tipo di cose è con un nuovo contesto di oggetti gestiti. Si crea un contesto dell'oggetto gestito con lo stesso archivio persistente:

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

Quindi aggiungi nuovi oggetti, li muti, ecc.

Quando arriva il momento di salvare, è necessario chiamare [tempContext save: ...] in tempContext e gestire la notifica di salvataggio per unirla al contesto originale. Per eliminare gli oggetti, rilascia questo contesto temporaneo e dimenticalo.

Quindi, quando salvi il contesto temporaneo, le modifiche vengono mantenute nello store e devi solo riportare quelle modifiche nel tuo contesto principale:

/* 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];

Questo è anche il modo in cui dovresti gestire le operazioni di dati di base multi-thread. Un contesto per thread.

Se devi accedere a oggetti esistenti da questo contesto temporaneo (per aggiungere relazioni, ecc.), Allora devi utilizzare l'ID dell'oggetto per ottenere una nuova istanza come questa:

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

Se provi a utilizzare un file NSManagedObject nel contesto sbagliato, otterrai delle eccezioni durante il salvataggio.


Creare un secondo contesto solo per questo è molto dispendioso in quanto stare in piedi NSManagedObjectContextè costoso sia in memoria che in CPU. Mi rendo conto che questo era originariamente in alcuni degli esempi Apple, ma hanno aggiornato e corretto quegli esempi.
Marcus S. Zarra

2
Apple sta ancora utilizzando questa tecnica (creando un secondo contesto di oggetti gestiti) per il codice di esempio CoreDataBooks.
nevan king

1
Nota Apple ha aggiornato CoreDataBooks, infatti utilizza ancora due contesti, ma ora il 2 ° contesto è figlio del primo. Questa tecnica è discussa (e consigliata) nella presentazione 303 del WWDC 2011 (cosa c'è di nuovo in Core Data in iOS) ed è menzionata qui (con il codice molto, MOLTO più semplice per unire le modifiche verso l'alto) stackoverflow.com/questions/9791469/…
Rabarbaro

4
"Creare un secondo contesto solo per questo è molto dispendioso in quanto la creazione di un NSManagedObjectContext è costosa sia in memoria che in CPU." . No non lo è. Le dipendenze del coordinatore del negozio persistente (modello a oggetti gestito e negozi concreti) non sono il contesto. I contesti sono leggeri.
quellish

3
@quellish d'accordo. Apple ha affermato nei suoi recenti colloqui sulle prestazioni dei dati di base al WWDC che la creazione di contesti è molto leggera.
Jesse

9

La creazione di oggetti temporanei da un contesto nullo funziona bene finché non si tenta effettivamente di avere una relazione con un oggetto il cui contesto! = Nil!

assicurati di essere d'accordo con quello.


Non mi va bene
Charlie

8

Quello che stai descrivendo è esattamente a cosa NSManagedObjectContextserve.

Da Core Data Programming Guide: Core Data Basics

Puoi pensare a un contesto oggetto gestito come un blocco appunti intelligente. Quando si recuperano oggetti da un archivio persistente, si portano copie temporanee sul blocco appunti dove formano un oggetto grafico (o una raccolta di oggetti grafici). Puoi quindi modificare quegli oggetti come preferisci. A meno che non si salvi effettivamente tali modifiche, tuttavia, l'archivio persistente rimane inalterato.

E Guida alla programmazione dei dati di base: convalida degli oggetti gestiti

Ciò è anche alla base dell'idea di un contesto di oggetto gestito che rappresenta un "blocco appunti": in generale è possibile portare gli oggetti gestiti sul blocco appunti e modificarli come si desidera prima di eseguire il commit delle modifiche o di eliminarli.

NSManagedObjectContextsono progettati per essere leggeri. Puoi crearli e scartarli a piacimento: è il coordinatore dei negozi persistenti e le dipendenze che sono "pesanti". A un singolo coordinatore del negozio persistente possono essere associati molti contesti. Con il modello di confinamento dei thread più vecchio e obsoleto ciò significherebbe impostare lo stesso coordinatore del negozio persistente in ogni contesto. Oggi significherebbe connettere contesti nidificati a un contesto radice associato al coordinatore del negozio persistente.

Crea un contesto, crea e modifica gli oggetti gestiti all'interno di quel contesto. Se desideri mantenerli e comunicare tali modifiche, salva il contesto. Altrimenti scartalo.

Il tentativo di creare oggetti gestiti indipendenti da un NSManagedObjectContextrichiede problemi. Ricorda che Core Data è in definitiva un meccanismo di rilevamento delle modifiche per un oggetto grafico. Per questo motivo, gli oggetti gestiti fanno davvero parte del contesto degli oggetti gestiti . Il contesto osserva il loro ciclo di vita e senza il contesto non tutte le funzionalità degli oggetti gestiti funzioneranno correttamente.


6

A seconda dell'utilizzo dell'oggetto temporaneo, ci sono alcune avvertenze per le raccomandazioni di cui sopra. Il mio caso d'uso è che voglio creare un oggetto temporaneo e associarlo alle viste. Quando l'utente sceglie di salvare questo oggetto, desidero impostare le relazioni con gli oggetti esistenti e salvare. Voglio farlo per evitare di creare un oggetto temporaneo per contenere quei valori. (Sì, potrei solo aspettare che l'utente salvi e quindi prenda il contenuto della vista, ma sto inserendo queste viste all'interno di una tabella e la logica per farlo è meno elegante.)

Le opzioni per gli oggetti temporanei sono:

1) (Preferito) Crea l'oggetto temporaneo in un contesto figlio. Questo non funzionerà perché sto associando l'oggetto all'interfaccia utente e non posso garantire che le funzioni di accesso all'oggetto vengano chiamate nel contesto figlio. (Non ho trovato documentazione che dichiari diversamente, quindi devo presumere.)

2) Creare l'oggetto temporaneo con un contesto oggetto nullo. Questo non funziona e provoca perdita / danneggiamento dei dati.

La mia soluzione: ho risolto questo problema creando l'oggetto temporaneo con un contesto di oggetto nullo, ma quando salvo l'oggetto, invece di inserirlo come # 2, copio tutti i suoi attributi in un nuovo oggetto che creo nel contesto principale. Ho creato un metodo di supporto nella mia sottoclasse NSManagedObject chiamato cloneInto: che mi consente di copiare facilmente attributi e relazioni per qualsiasi oggetto.


È quello che sto cercando. Ma il mio dubbio è come gestirai gli attributi della relazione?
Mani

1

Per me la risposta di Marcus non ha funzionato. Ecco cosa ha funzionato per me:

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

quindi, se decido di salvarlo:

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

Inoltre, non dobbiamo dimenticare di rilasciarlo

[unassociatedObject release]

1

Sto riscrivendo questa risposta per Swift come tutte le domande simili per un rapido reindirizzamento a questa domanda.

È possibile dichiarare l'oggetto senza ManagedContext utilizzando il codice seguente.

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

Successivamente, per salvare l'oggetto è possibile inserirlo nel contesto e salvarlo.

myContext.insert(unassociatedObject)
// Saving the object
do {
    try self.stack.saveContext()
    } catch {
        print("save unsuccessful")
    }
}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.