How to Deal with Temporary NSManagedObject instances?

Solution 1:

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
}

Solution 2:

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.

Solution 3:

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.

Solution 4:

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.