如何处理临时 NSManagedObject 实例?

我需要创建 NSManagedObject实例,用它们做一些事情,然后将它们删除或存储到 sqlite db。问题是,我不能创建未连接到 NSManagedObjectContextNSManagedObject实例,这意味着在我决定不需要数据库中的某些对象之后,我必须设法清除这些实例。

为了解决这个问题,我使用相同的协调器创建了一个内存中的存储,并且通过使用 assignObject:toPersistentStore.将临时对象放置在那里。现在,我如何确保这些临时对象不会到达数据,我从公共存储上下文中获取这些数据?或者我必须为这样的任务创建单独的上下文?


UPD:

现在我正在考虑为内存存储创建单独的上下文。如何将对象从一个上下文移动到另一个上下文?仅仅使用[ context insert tObject: ] ?在这个设置中它能正常工作吗?如果我从对象图中插入一个对象,是否整个图也会插入到上下文中?

35544 次浏览

The correct way to achieve this sort of thing is with a new managed object context. You create a managed object context with the same persistent store:

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

Then you add new objects, mutate them, etc.

When it comes time to save, you need to call [tempContext save:...] on the tempContext, and handle the save notification to merge that into your original context. To discard the objects, just release this temporary context and forget about it.

So when you save the temporary context, the changes are persisted to the store, and you just need to get those changes back into your main context:

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

This is also the way you should handle multi-threaded core data operations. One context per thread.

If you need to access existing objects from this temporary context (to add relations etc.) then you need to use the object's ID to get a new instance like this:

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

If you try to use an NSManagedObject in the wrong context you will get exceptions while saving.

NOTE: This answer is very old. See comments for full history. My recommendation has since changed and I no longer recommend using unassociated NSManagedObject instances. My current recommendation is to use temporary child NSManagedObjectContext instances.

Original Answer

The easiest way to do this is to create your NSManagedObject instances without an associated NSManagedObjectContext.

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

Then when you want to save it:

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

For me Marcus's answer didn't work. Here's what worked for me:

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

then, if I decide to save it:

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

We must also not forget to release it

[unassociatedObject release]

Creating temporary objects from nil context works fine until you actually try to have a relationship with an object whose context != nil!

make sure your okay with that.

iOS5 provides a simpler alternative to Mike Weller's answer. Instead use a child NSManagedObjectContext. It removes the need to trampoline through NSNotificationCenter

To create a child context:

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

Then create your objects using the child context:

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

The changes are only applied when the child context is saved. So to discard the changes just do not save.

There is still a limitation on relationships. ie You can't create relationships to objects in other contexts. To get around this use objectID's, to get the object from the child context. eg.

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

Note, saving the child context applies the changes to the parent context. Saving the parent context persists the changes.

See wwdc 2012 session 214 for a full explanation.

What you are describing is exactly what an NSManagedObjectContextis for.

From Core Data Programming Guide: Core Data Basics

You can think of a managed object context as an intelligent scratch pad. When you fetch objects from a persistent store, you bring temporary copies onto the scratch pad where they form an object graph (or a collection of object graphs). You can then modify those objects however you like. Unless you actually save those changes, however, the persistent store remains unaltered.

And Core Data Programming Guide: Managed Object Validation

This also underpins the idea of a managed object context representing a "scratch pad"—in general you can bring managed objects onto the scratch pad and edit them however you wish before ultimately either committing the changes or discarding them.

NSManagedObjectContexts are designed to be lightweight. You can create and discard them at will - it's the persistent stores coordinator and it's dependancies that are "heavy". A single persistent store coordinator can have many contexts associated with it. Under the older, obsolete thread confinement model this would mean setting the same persistent store coordinator on each context. Today it would mean connecting nested contexts to a root context that is associated with the persistent store coordinator.

Create a context, create and modify managed objects within that context. If you want to persist them and communicate those changes, save the context. Otherwise discard it.

Attempting to create managed objects independent of an NSManagedObjectContext is asking for trouble. Remember that Core Data is ultimately a change tracking mechanism for an object graph. Because of this, managed objects are really part of the managed object context. The context observes their life cycle, and without the context not all of the managed object functionality will work correctly.

Depending on your use of the temporary object there are some caveats to the above recommendations. My use case is that I want to create a temporary object and bind it to views. When the user opts to save this object, I want to setup relationships to existing object(s) and save. I want to do this to avoid creating a temporary object to hold those values. (Yes, I could just wait until the user saves and then grab the view contents but I'm putting these views inside of a table and the logic to do this is less elegant.)

The options for temporary objects are:

1) (Preferred) Create the temporary object in a child context. This won't work because I'm binding the object to the UI and I can't guarantee the object accessors are called on the child context. (I have found no documentation that states otherwise so I have to assume.)

2) Create the temporary object with nil object context. This doesn't work and results in data loss/corruption.

My Solution: I solved this by creating the temporary object with nil object context but when I save the object, rather than inserting it as #2, I copy all of it's attributes into a new object that I create in the main context. I created a supporting method in my NSManagedObject subclass called cloneInto: that lets me copy attributes and relationships easily for any object.

I am rewriting this answer for Swift as all similar questions for swift redirect to this question.

You can declare the object without any ManagedContext using the following code.

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

Later on, to save the object you can insert it into the context and save it.

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