How to Deal with Temporary NSManagedObject instances?
Asked Answered
H

8

89

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?

Hesperidium answered 15/7, 2010 at 13:56 Comment(2)
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.Caylor
UPD section is now not relevant, cause i've chosen another approach, see my last comment to your answer.Hesperidium
C
148

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
}
Caylor answered 15/7, 2010 at 18:2 Comment(31)
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?Hesperidium
It is smart enough to handle the relationships as well.Caylor
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.Hesperidium
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 okCopperhead
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 notCopperhead
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.Caylor
@MarcusS.Zarra Is it possible to fetch these created temporary objects (which do not belong to a context)?Heterogeneous
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.Caylor
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.Vesting
This doesn't seem to work well with iOS 8, especially with persisting relationships. Can anyone else confirm this?Gustavogustavus
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.Sneed
@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.Chromato
@Chromato that is an odd situation and should not happen. Can you duplicate it in a test project?Caylor
@Marcin, I'm seeing the same issue on iOS 8: I create the object by using -[initWithEntity:<myEntity> 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.Schoolgirl
@Schoolgirl Can you duplicate it in a test project? That way we can verify it and if need be, use it for a radar.Caylor
@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?Housewarming
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.Caylor
@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 anywhereMarmion
Added to the list! :)Caylor
@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 youDunite
@Dunite 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 :)Caylor
@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)Dunite
@Dunite 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.Caylor
@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!Pamper
@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 :)Caylor
Hi @MarcusS.Zarra, can you update your answer with additional details by explaining the child context with an example?Triclinic
@Triclinic What is there to demonstrate? Create a child context and don't save it. I am not sure what you are asking for.Caylor
@MarcusS.Zarra let's say I created a child context with lots of temporaries. Then I want to save one of them and discard the rest. How would I achieve that?Lippert
@AyxanHaqverdili Call save on the one child (which will roll its changes up to its parent). QED. As long as you do not save the other children, their changes won't be impacted. If you have multiple children interacting with the same data sets; those other children can become stale; otherwise, discard them. Save the parent when it makes sense in the flow of your application's logic.Caylor
@MarcusS.Zarra are you suggesting having one child MOC per MO? That sounds a bit wasteful as I might have a couple thousand MOs?Lippert
@MarcusS.Zarra please see my question here https://mcmap.net/q/246317/-coredata-save-some-changes-from-child-managed-object-context/10147399Lippert
A
41

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.

Aniakudo answered 24/1, 2013 at 4:43 Comment(5)
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 github.com/seltzered/CoreDataMagicalRecordTempObjectsDemoBenniebenning
Which is moc in the third snippet? Is it childContext or myMangedObjectContext?Snuffbox
It is the childContextAniakudo
this solution is better than having the nil context.Hyperbolize
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;.Downcome
L
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.

Leukas answered 15/7, 2010 at 14:37 Comment(9)
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.Caylor
> 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.Capitalist
Apple is still using this technique (creating a second managed object context) for the CoreDataBooks example code.Corticate
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) #9791969Copperhead
"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.Leucomaine
@Leucomaine Agreed. Apple has stated in their recent core data performance talks at WWDC that creating contexts is very lightweight.Overtone
@Overtone this has always been the case. It's not a recent change.Leucomaine
@Leucomaine Agreed, just wanted to support your comment and help dispel the myth that context creation is expensive.Overtone
@Leucomaine 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.Hendon
U
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.

Uncouple answered 3/2, 2012 at 12:55 Comment(1)
I'm not okay with thatAarika
L
9

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.

Leucomaine answered 15/8, 2014 at 10:33 Comment(0)
S
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.

Sympathize answered 9/2, 2015 at 19:8 Comment(1)
That's what I'm looking for. But my doubt is how will you handle the relationship attributes?Barraza
R
3

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")
    }
}
Roger answered 17/11, 2017 at 10:49 Comment(0)
G
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]
Gorky answered 28/7, 2011 at 9:49 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.