Wie gehe ich mit temporären NSManagedObject-Instanzen um?

86

Ich muss NSManagedObjectInstanzen erstellen , einige Dinge mit ihnen machen und sie dann in den Papierkorb werfen oder in SQLite-Datenbank speichern. Das Problem ist, ich kann nicht Instanzen schafft NSManagedObjectnicht verbunden NSManagedObjectContextund das bedeutet , ich habe irgendwie zu klären , nachdem ich entscheiden , dass ich einige der Objekte in meiner db nicht brauchen.

Um damit umzugehen, habe ich mit demselben Koordinator einen In-Memory-Speicher erstellt und platziere temporäre Objekte dort mit assignObject:toPersistentStore.Now. Wie stelle ich sicher, dass diese temporären Objekte nicht an die Daten gelangen, die ich aus dem abrufe gemeinsam für beide Geschäfte Kontext? Oder muss ich für eine solche Aufgabe separate Kontexte erstellen?


UPD:

Jetzt denke ich darüber nach, einen separaten Kontext für den In-Memory-Speicher zu erstellen. Wie verschiebe ich Objekte von einem Kontext in einen anderen? Nur mit [context insertObject:]? Funktioniert es in diesem Setup einwandfrei? Wenn ich ein Objekt aus dem Objektdiagramm einfüge, wird das gesamte Diagramm auch in den Kontext eingefügt?

fspirit
quelle
Dies sollte eine separate Frage sein, da Sie diese als beantwortet markiert haben. Erstellen Sie eine neue Frage und erklären warum Sie glauben , einen separaten gesamten Core Data benötigen Stapel NUR für eine In-Memory - Speicher. Gerne erkunde ich die Frage mit Ihnen.
Marcus S. Zarra
Der UPD-Abschnitt ist jetzt nicht relevant, da ich einen anderen Ansatz gewählt habe. Siehe meinen letzten Kommentar zu Ihrer Antwort.
Geist

Antworten:

146

HINWEIS: Diese Antwort ist sehr alt. Siehe Kommentare für die vollständige Geschichte. Meine Empfehlung hat sich seitdem geändert und ich empfehle nicht mehr, nicht zugeordnete NSManagedObjectInstanzen zu verwenden. Meine aktuelle Empfehlung ist die Verwendung temporärer untergeordneter NSManagedObjectContextInstanzen.

Ursprüngliche Antwort

Der einfachste Weg, dies zu tun, besteht darin, Ihre NSManagedObjectInstanzen ohne zugehörige zu erstellen NSManagedObjectContext.

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

Dann, wenn Sie es speichern möchten:

[myMOC insertObject:unassociatedObject];
NSError *error = nil;
if (![myMoc save:&error]) {
  //Respond to the error
}
Marcus S. Zarra
quelle
6
Wenn unassociatedObject Verweise auf andere nicht zugeordnete Objekte hat, sollte ich sie einzeln einfügen, oder myMOC ist intelligent genug, um alle Verweise zu sammeln und sie auch einzufügen?
Geist
6
Es ist klug genug, auch mit den Beziehungen umzugehen.
Marcus S. Zarra
2
Ich finde es gut, dass Sie mit diesem Ansatz die MOs wie normale Datenobjekte behandeln können, bevor Sie sie speichern. Ich bin jedoch besorgt darüber, wie "unterstützt" der CoreData-Vertrag ist und wie zukunftssicher er ist. Erwähnt oder verwendet Apple diesen Ansatz irgendwo? Wenn nicht, könnte eine zukünftige iOS-Version die dynamischen Eigenschaften so ändern, dass sie vom MOC abhängen, und diesen Ansatz unterbrechen. Die Apple-Dokumente sind sich darüber nicht klar: Sie betonen die Wichtigkeit des Kontexts und des festgelegten Initialisierers, aber es gibt eine Erwähnung im MO-Dokument, die besagt: "Wenn der Kontext nicht Null ist, dann ...", was darauf hindeutet, dass Null in Ordnung sein könnte
Rhabarber
41
Ich habe diesen Ansatz vor einiger Zeit verwendet, aber merkwürdige Verhaltensweisen und Abstürze festgestellt, als ich diese Objekte geändert und / oder Beziehungen für sie erstellt habe, bevor ich sie in ein MOC eingefügt habe. Ich habe dies mit einem Core Data-Ingenieur bei WWDC besprochen und er sagte, dass die API für nicht zugeordnete Objekte zwar vorhanden ist, er jedoch dringend davon abrät, sie als MOC zu verwenden, da er sich stark auf KVO-Benachrichtigungen stützt, die von seinen Objekten gesendet werden. Er schlug vor, reguläres NSObject für temporäre Objekte zu verwenden, da dies viel sicherer ist.
Adrian Schönig
7
Dies scheint unter iOS 8 nicht gut zu funktionieren, insbesondere bei dauerhaften Beziehungen. Kann das noch jemand bestätigen?
Janum Trivedi
39

iOS5 bietet eine einfachere Alternative zu Mike Wellers Antwort. Verwenden Sie stattdessen ein Kind NSManagedObjectContext. Das Trampolin durch das NSNotificationCenter entfällt

So erstellen Sie einen untergeordneten Kontext:

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

Erstellen Sie dann Ihre Objekte im untergeordneten Kontext:

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

Die Änderungen werden nur angewendet, wenn der untergeordnete Kontext gespeichert wird. Um die Änderungen zu verwerfen, speichern Sie sie einfach nicht.

Es gibt immer noch eine Einschränkung für Beziehungen. dh Sie können keine Beziehungen zu Objekten in anderen Kontexten erstellen. Um dies zu umgehen, verwenden Sie Objekt-IDs, um das Objekt aus dem untergeordneten Kontext abzurufen. z.B.

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

Beachten Sie, dass beim Speichern des untergeordneten Kontexts die Änderungen auf den übergeordneten Kontext angewendet werden. Durch Speichern des übergeordneten Kontexts bleiben die Änderungen erhalten.

Eine vollständige Erklärung finden Sie unter wwdc 2012, Sitzung 214 .

Eisenbahnparade
quelle
1
Vielen Dank für den Vorschlag! Ich habe eine Demo geschrieben, in der diese Methode im Vergleich zur Verwendung eines Nullkontexts getestet wurde. Zumindest unter OSX funktionierte dies beim Einfügen eines Nullkontexts, der beim Speichern seine Attribute verlor. Demo unter github.com/seltzered/CoreDataMagicalRecordTempObjectsDemo
Vivek Gani
Welches ist mocim dritten Ausschnitt? Ist es childContextoder myMangedObjectContext?
Bugloaf
Es ist der childContext
Eisenbahnparade
Diese Lösung ist besser als der Null-Kontext.
Will Y
Da NSManagedObjectbereits das Relevante bereitgestellt wird NSManagedObjectContext, können Sie die Auswahl des Kontexts automatisieren: NSManagedObject* objectRelatedContextually = [objectWithRelationship.managedObjectContext objectWithID:objectRelated.objectID];und dann objectWithRelationship.relationship = objectRelatedContextually;.
Gary
9

Der richtige Weg, um dies zu erreichen, ist ein neuer Kontext für verwaltete Objekte. Sie erstellen einen verwalteten Objektkontext mit demselben persistenten Speicher:

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

Dann fügen Sie neue Objekte hinzu, mutieren sie usw.

Wenn es Zeit zum Speichern ist, müssen Sie [tempContext save: ...] im tempContext aufrufen und die Speicherbenachrichtigung verarbeiten, um diese in Ihren ursprünglichen Kontext einzufügen. Um die Objekte zu verwerfen, geben Sie einfach diesen temporären Kontext frei und vergessen Sie ihn.

Wenn Sie also den temporären Kontext speichern, bleiben die Änderungen im Geschäft erhalten, und Sie müssen diese Änderungen nur wieder in Ihren Hauptkontext übernehmen:

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

Auf diese Weise sollten Sie auch Multithread-Kerndatenoperationen behandeln. Ein Kontext pro Thread.

Wenn Sie aus diesem temporären Kontext auf vorhandene Objekte zugreifen müssen (um Beziehungen usw. hinzuzufügen), müssen Sie die ID des Objekts verwenden, um eine neue Instanz wie die folgende zu erhalten:

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

Wenn Sie versuchen, eine NSManagedObjectim falschen Kontext zu verwenden, werden beim Speichern Ausnahmen angezeigt.

Mike Weller
quelle
Das Erstellen eines zweiten Kontexts nur dafür ist sehr verschwenderisch, da das Aufstehen von a NSManagedObjectContextsowohl im Speicher als auch in der CPU teuer ist. Mir ist klar, dass dies ursprünglich in einigen Apple-Beispielen der Fall war, aber diese Beispiele wurden aktualisiert und korrigiert.
Marcus S. Zarra
2
Apple verwendet diese Technik (Erstellen eines zweiten Kontexts für verwaltete Objekte) weiterhin für den CoreDataBooks-Beispielcode.
Nevan King
1
Hinweis Apple hat CoreDataBooks aktualisiert, es werden zwar immer noch zwei Kontexte verwendet, aber jetzt ist der zweite Kontext ein Kind des ersten. Diese Technik wird in der WWDC 2011-Präsentation 303 (was ist neu in Core Data in iOS) besprochen (und empfohlen) und hier erwähnt (mit dem viel, viel einfacheren Code zum Zusammenführen von Änderungen nach oben) stackoverflow.com/questions/9791469/…
Rhabarber
4
"Das Erstellen eines zweiten Kontexts nur dafür ist sehr verschwenderisch, da das Aufstehen eines NSManagedObjectContext sowohl im Speicher als auch in der CPU teuer ist." . Nein, ist es nicht. Die Abhängigkeiten des persistenten Geschäftskoordinators (verwaltetes Objektmodell und konkrete Geschäfte) sind nicht der Kontext. Kontexte sind leichtgewichtig.
quellish
3
@quellish Einverstanden. Apple hat in seinen jüngsten Gesprächen über die Leistung von Kerndaten auf der WWDC festgestellt, dass das Erstellen von Kontexten sehr einfach ist.
Jesse
9

Das Erstellen temporärer Objekte aus dem Nullkontext funktioniert einwandfrei, bis Sie tatsächlich versuchen, eine Beziehung zu einem Objekt herzustellen, dessen Kontext! = Null!

Stellen Sie sicher, dass Sie damit einverstanden sind.

user134611
quelle
Ich bin damit nicht einverstanden
Charlie
8

Was Sie beschreiben, ist genau das, wofür ein NSManagedObjectContextist.

Aus dem Core Data Programming Guide: Grundlagen der Core Data

Sie können sich einen verwalteten Objektkontext als einen intelligenten Notizblock vorstellen. Wenn Sie Objekte aus einem dauerhaften Speicher abrufen, bringen Sie temporäre Kopien auf den Notizblock, wo sie ein Objektdiagramm (oder eine Sammlung von Objektdiagrammen) bilden. Sie können diese Objekte dann beliebig ändern. Sofern Sie diese Änderungen nicht tatsächlich speichern, bleibt der persistente Speicher unverändert.

Und Core Data Programming Guide: Validierung verwalteter Objekte

Dies untermauert auch die Idee eines verwalteten Objektkontexts, der einen "Notizblock" darstellt. Im Allgemeinen können Sie verwaltete Objekte auf den Notizblock bringen und nach Belieben bearbeiten, bevor Sie die Änderungen endgültig übernehmen oder verwerfen.

NSManagedObjectContexts sind leicht ausgelegt. Sie können sie nach Belieben erstellen und verwerfen - es ist der Koordinator für beständige Geschäfte und die Abhängigkeiten, die "schwer" sind. Einem einzelnen persistenten Geschäftskoordinator können viele Kontexte zugeordnet sein. Unter dem älteren, veralteten Thread-Beschränkungsmodell würde dies bedeuten, dass für jeden Kontext derselbe persistente Speicherkoordinator festgelegt wird. Heute würde dies bedeuten, verschachtelte Kontexte mit einem Stammkontext zu verbinden, der dem persistenten Speicherkoordinator zugeordnet ist.

Erstellen Sie einen Kontext, erstellen und ändern Sie verwaltete Objekte in diesem Kontext. Wenn Sie sie beibehalten und diese Änderungen mitteilen möchten, speichern Sie den Kontext. Andernfalls verwerfen Sie es.

Der Versuch, verwaltete Objekte unabhängig von einem zu erstellen, NSManagedObjectContextist problematisch. Denken Sie daran, dass Core Data letztendlich ein Änderungsverfolgungsmechanismus für ein Objektdiagramm ist. Aus diesem Grund sind verwaltete Objekte wirklich Teil des Kontexts für verwaltete Objekte . Der Kontext beobachtet ihren Lebenszyklus , und ohne den Kontext funktionieren nicht alle Funktionen des verwalteten Objekts ordnungsgemäß.

quellish
quelle
6

Abhängig von Ihrer Verwendung des temporären Objekts gibt es einige Einschränkungen bei den obigen Empfehlungen. Mein Anwendungsfall ist, dass ich ein temporäres Objekt erstellen und es an Ansichten binden möchte. Wenn der Benutzer dieses Objekt speichert, möchte ich Beziehungen zu vorhandenen Objekten einrichten und speichern. Ich möchte dies tun, um zu vermeiden, dass ein temporäres Objekt erstellt wird, das diese Werte enthält. (Ja, ich könnte einfach warten, bis der Benutzer speichert, und dann den Ansichtsinhalt abrufen, aber ich füge diese Ansichten in eine Tabelle ein, und die Logik dafür ist weniger elegant.)

Die Optionen für temporäre Objekte sind:

1) (Bevorzugt) Erstellen Sie das temporäre Objekt in einem untergeordneten Kontext. Dies funktioniert nicht, da ich das Objekt an die Benutzeroberfläche binde und nicht garantieren kann, dass die Objekt-Accessoren im untergeordneten Kontext aufgerufen werden. (Ich habe keine Dokumentation gefunden, in der etwas anderes angegeben ist, daher muss ich davon ausgehen.)

2) Erstellen Sie das temporäre Objekt mit dem Objektkontext Null. Dies funktioniert nicht und führt zu Datenverlust / -beschädigung.

Meine Lösung: Ich habe dieses Problem gelöst, indem ich das temporäre Objekt mit dem Objektkontext Null erstellt habe. Wenn ich das Objekt jedoch speichere, anstatt es als Nummer 2 einzufügen, kopiere ich alle seine Attribute in ein neues Objekt, das ich im Hauptkontext erstelle. Ich habe in meiner NSManagedObject-Unterklasse eine unterstützende Methode namens cloneInto erstellt, mit der ich Attribute und Beziehungen für jedes Objekt einfach kopieren kann.

greg
quelle
Das suche ich. Aber ich bezweifle, wie Sie mit den Beziehungsattributen umgehen werden.
Mani
1

Für mich hat Marcus 'Antwort nicht funktioniert. Folgendes hat bei mir funktioniert:

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

dann, wenn ich mich entscheide, es zu speichern:

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

Wir dürfen auch nicht vergessen, es freizugeben

[unassociatedObject release]
Lucas
quelle
1

Ich schreibe diese Antwort für Swift neu, da alle ähnlichen Fragen schnell auf diese Frage umgeleitet werden.

Mit dem folgenden Code können Sie das Objekt ohne ManagedContext deklarieren.

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

Um das Objekt später zu speichern, können Sie es in den Kontext einfügen und speichern.

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