86

I need to create NSManagedObject instances, do some stuff with them and then trash them or store to sqlite db. The problem is, I cannot create instances of NSManagedObject unconnected to NSManagedObjectContext and this means I have to clear up somehow after I decide that I don't need some of the objects in my db.

To deal with it, I have created an in-memory store using the same coordinator and I'm placing temporary objects there by using assignObject:toPersistentStore. Now, how do I ensure that these temporary objects don't get to the data, which I fetch from the common to both stores context? Or do I have to create separate contexts for such a task?


UPD:

Now I'm thinking about making separate context for in-memory store. How do I move objects from one context to another? Just using [context insertObject:]? Will it work OK in this setup? If I insert one object from the graph of objects, does the whole graph also get inserted into context?

Andriy
  • 2,727
  • 2
  • 18
  • 29
fspirit
  • 2,437
  • 2
  • 18
  • 26
  • This should be a separate question since you have flagged this one as answered. Create a new question and explain *WHY* you feel you need a separate entire Core Data stack **JUST** for an in-memory store. I will be happy to explore the question with you. – Marcus S. Zarra Jul 16 '10 at 18:48
  • UPD section is now not relevant, cause i've chosen another approach, see my last comment to your answer. – fspirit Jul 19 '10 at 12:22

8 Answers8

147

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
}
Marcus S. Zarra
  • 46,143
  • 9
  • 99
  • 181
  • 6
    If unassociatedObject has refs to other unassociated objects, should I insert them one by one or myMOC is smart enough to collect all refs and insert them also? – fspirit Jul 15 '10 at 20:17
  • 6
    It is smart enough to handle the relationships as well. – Marcus S. Zarra Jul 15 '10 at 22:50
  • 1
    I've found that in my situation it's enough to delete unwanted objects from the context, when needed, cause there are much more objects from the initial scope that will be persisted. If I meet the inverse situation, I'll go with your solution. – fspirit Jul 16 '10 at 07:09
  • 2
    I like that this approach lets you treat the MOs like regular data objects before you decide to store them but am worried about how "supported" by the CoreData contract and therefore how futureproof it is. Does apple mention or use this approach anywhere? Because if not, a future iOS release could change the dynamic properties to depend on the MOC and break this approach. The apple docs are not clear on this: they stress the importance of the context and the designated initializer, but there is one mention in the MO doc saying "if context is not nil, then..." suggesting that nil might be ok – Rhubarb Sep 27 '12 at 11:48
  • In contrast, the MOC deleteObject approach suggested elsewhere and by fspirit is documented: the doc for deleteObject makes it clear that it works whether the object was ever saved or not – Rhubarb Sep 27 '12 at 11:53
  • This approach **IS** documented by Apple and is part of the contract. Review the documentation on the method and you will see it referenced. I have been using it since Core Data was in beta, it has not changed and we will get ample warning via beta releases if it is ever changed in the future. No API is future proof. – Marcus S. Zarra Sep 27 '12 at 15:05
  • @MarcusS.Zarra Is it possible to fetch these created temporary objects (which do not belong to a context)? – Peter Warbo Jul 04 '13 at 09:06
  • No. This design is to handle throw away MOs that are either going to get saved immediately or destroyed immediately. If you want them to survive longer then they need to get associated with a context. – Marcus S. Zarra Jul 04 '13 at 17:31
  • 41
    I used this approach a while ago but started seeing strange behaviour and crashes when I modified those objects and/or created relationships for them before inserting them into an MOC. I talked this through with a Core Data engineer at WWDC and he said that while the API for unassociated objects is there, he strongly recommended against using it as an MOC heavily relies on KVO notifications sent by its objects. He suggested to use regular NSObject for temporary objects as that is much safer. – Adrian Schönig Sep 23 '13 at 22:28
  • 7
    This doesn't seem to work well with iOS 8, especially with persisting relationships. Can anyone else confirm this? – Janum Trivedi Aug 20 '14 at 01:05
  • 1
    I also used NSManagedObjects with a nil context and it worked very well... for a while... But some weeks later now I recognized the problem. When unassociated objects get persisted, they will be inserted in a context and saved to the persistent store. If then the app is terminated and restarted again, these objects are loaded from the persistent store and in that moment they are associated to the persistent context. From now there occur crashes when i.e. I try to assign a relationship from an unaccociated object to the associated one. My new approach now will be nested, temporary contexts. – blackjacx Oct 17 '14 at 10:44
  • @JanumTrivedi I can't make it work with iOS 8.1. Object is inserted somehow, but fail on save because every value somehow is nil'ed. It should work but it;s not. – Marcin Nov 22 '14 at 14:58
  • @Marcin that is an odd situation and should not happen. Can you duplicate it in a test project? – Marcus S. Zarra Nov 24 '14 at 15:53
  • 1
    @Marcin, I'm seeing the same issue on iOS 8: I create the object by using -[initWithEntity: insertIntoManagedObjectContext:nil]. I then populate the object, use -[NSManagedObjectContext insertObject:] to insert it into the context. The object is inserted, but then out of the blue the context tries to insert an uninitialized version of the same object. Not sure what's going on with it. – stiggs Jan 21 '15 at 04:04
  • 1
    @stiggs Can you duplicate it in a test project? That way we can verify it and if need be, use it for a radar. – Marcus S. Zarra Jan 21 '15 at 15:52
  • 2
    @AdrianSchönig So then we should use NSObject for temp purposes. Wouldn't that replicate our models (one for temp purpose and one for CoreDate purpose.). Is there a way without duplicating the models? – Saad Masood Jun 07 '15 at 22:25
  • 2
    No, you should use managed objects, perhaps in an in-memory store if needed. There is no reason to avoid using Core Data for temporary objects. Core Data is designed to **be** your model and it **can** persist. – Marcus S. Zarra Jun 07 '15 at 23:40
  • @MarcusS.Zarra Thanks Marcus for all suggestion. Can you get some time to write a blog on this topic. It's really common issue with core data and I have not found it anywhere – codester Jun 26 '15 at 07:00
  • Added to the list! :) – Marcus S. Zarra Jun 30 '15 at 03:21
  • @MarcusS.Zarra Hi Marcus, I tried to insert unassociated entity with OneToMany relationship (set of unassociated entities) to context and those entities in set wasn't inserted into context (they had .managedObjectContext property nil) so when I tried to save context you got error. Of course I can insert them all but its boilerplate and I'm concerned about performance. Are you sure that it's working for you? Thank you – beretis Oct 26 '17 at 10:53
  • @beretis There isn't a solid reason to use a unassociated managed object; I don't use them that way. I always associate them and then delete them before a save if it turns out I don't need them. If you are having an issue I suggest duplicating the issue in a test case so that you can isolate the issue. Then you can share that test case with me and Apple :) – Marcus S. Zarra Oct 26 '17 at 14:51
  • 1
    @MarcusS.Zarra Now Im just confused, you were promoting NSManagedObjects without context as a temp model classes. So I fetch data from server, parse them into NSMO without context, and then I insert them into context exactly how you did in the answer. I thought it was an interesting idea how to deal with this issue (better then creating Data Transfer Objects) – beretis Oct 30 '17 at 10:28
  • 2
    @beretis When I wrote this answer *seven years ago* I did use `NSManagedObject` instances without a context. I no longer do as the landscape has changed drastically since then. Creating a context is no longer expensive, associating an `NSManagedObject` with a context is no longer expensive. The advice changes as the APIs change. If you are using Core Data there is never a reason to create data transfer objects. – Marcus S. Zarra Oct 30 '17 at 15:47
  • @MarcusS.Zarra: seeing your last comment, why do you then keep the answer as it is. I thought this is it until I reached this comment way down. I assume you suggest a child context by now. Someone else writes that this approach won't work with relationships, is that true? Thanks for caring - after 8 years! Thanks! – Wizard of Kneup Sep 20 '18 at 04:52
  • @WizardofKneup Thank you for calling that out! Answer updated. Child contexts work just fine with relationships, I would need to see a specific use case to understand what "someone" is having an issue with :) – Marcus S. Zarra Sep 20 '18 at 18:17
  • Hi @MarcusS.Zarra, can you update your answer with additional details by explaining the child context with an example? – Hemang May 29 '19 at 06:35
  • @Hemang What is there to demonstrate? Create a child context and don't save it. I am not sure what you are asking for. – Marcus S. Zarra May 30 '19 at 00:12
40

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.

railwayparade
  • 5,106
  • 1
  • 37
  • 49
  • 1
    Thanks for suggesting this! I wrote a demo testing this method versus using a nil context and at least on OSX, this worked while inserting a nil context lost its attributes when saving - demo at https://github.com/seltzered/CoreDataMagicalRecordTempObjectsDemo – Vivek Gani Jun 24 '14 at 06:47
  • Which is `moc` in the third snippet? Is it `childContext` or `myMangedObjectContext`? – bugloaf Oct 06 '14 at 19:32
  • It is the childContext – railwayparade Oct 07 '14 at 22:19
  • this solution is better than having the nil context. – Will Y Dec 24 '15 at 07:52
  • Since `NSManagedObject` already provides the relevant `NSManagedObjectContext`, you can automate the choice of context: `NSManagedObject* objectRelatedContextually = [objectWithRelationship.managedObjectContext objectWithID:objectRelated.objectID];` and then `objectWithRelationship.relationship = objectRelatedContextually;`. – Gary Feb 16 '16 at 11:44
9

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.

Mike Weller
  • 44,483
  • 14
  • 126
  • 148
  • Creating a second context just for this is very wasteful as standing up a `NSManagedObjectContext` is expensive in both memory and CPU. I realize this was originally in some of the Apple examples, but they have updated and corrected those examples. – Marcus S. Zarra Jul 16 '10 at 15:39
  • > Creating a second context just for > this is very wasteful as standing up a > NSManagedObjectContext is expensive in > both memory and CPU. I realize this > was originally in some of the Apple > examples, but they have updated and > corrected those examples. I'm wondering where I might find the Apple examples mentioned. Thank you. –  Mar 02 '11 at 13:28
  • 2
    Apple is still using this technique (creating a second managed object context) for the CoreDataBooks example code. – nevan king Apr 18 '11 at 11:14
  • 1
    Note Apple have updated CoreDataBooks, indeed it still uses two contexts, but now the 2nd context is a child of the first. This technique is discussed (and recommended) in WWDC 2011 presentation 303 (what's new in Core Data in iOS) and is mentioned here (with the much, MUCH, simpler code for merging changes upward) http://stackoverflow.com/questions/9791469/how-to-merge-changes-from-one-child-managed-object-context-to-another-via-a-pare – Rhubarb Sep 27 '12 at 17:06
  • 4
    "Creating a second context just for this is very wasteful as standing up a NSManagedObjectContext is expensive in both memory and CPU." . No, it's not. The persistent store coordinator's dependancies (managed object model and concrete stores) are, not the context. Contexts are lightweight. – quellish Aug 15 '14 at 10:35
  • 3
    @quellish Agreed. Apple has stated in their recent core data performance talks at WWDC that creating contexts is very lightweight. – Jesse Dec 04 '14 at 01:01
  • @Jesse this has always been the case. It's not a recent change. – quellish Dec 04 '14 at 01:02
  • @quellish Agreed, just wanted to support your comment and help dispel the myth that context creation is expensive. – Jesse Dec 05 '14 at 20:41
  • @quellish Thank you for that advice! I've flagged the original comment as it provides inaccurate advice which, if followed, would be actively harmful. I suggest others to do the same. – Benjohn May 27 '15 at 15:18
9

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.

user134611
  • 753
  • 1
  • 11
  • 21
8

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.

quellish
  • 20,584
  • 4
  • 72
  • 81
6

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.

greg
  • 1,876
  • 16
  • 26
  • That's what I'm looking for. But my doubt is how will you handle the relationship attributes? – Mani Oct 31 '17 at 06:34
1

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")
    }
}
Mitul Jindal
  • 550
  • 3
  • 14
1

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]
Lucas
  • 6,527
  • 3
  • 22
  • 41