60

I have a user interface to insert a Transaction. once the user clicks on a plus he gets the screen and i want to instantiate my Core Data NSManagedObject entity let the user work on it. Then when the user clicks on the Save button i will call the save function.

so down to code:

transaction = (Transaction *)[NSEntityDescription insertNewObjectForEntityForName:@"Transaction" inManagedObjectContext:self.managedObjectContext];
//even if i dont call save: its going to show up on my table
[self.managedObjectContext save:&error]

P.S i am using an NSFetchedResultsController on that table and I see that the NSFetchedResultsController is inserting a section and an object to the table.

My thought is if there is a way to instantiate the Transaction NSManagedObject i could update it with out saving untill the client choses to.

Eimantas
  • 48,927
  • 17
  • 132
  • 168
Edward Ashak
  • 2,411
  • 2
  • 23
  • 38

6 Answers6

37

For what it's worth, Marcus Zarra seems to be promoting the nil context approach, claiming that it's expensive to create a new context. For more details, see this answer to a similar question.

Update

I'm currently using the nil context approach and have encountered something that might be of interest to others. To create a managed object without a context, you use the initWithEntity:insertIntoManagedObjectContext: method of NSManagedObject. According to Apple's documentation for this method:

If context is not nil, this method invokes [context insertObject:self] (which causes awakeFromInsert to be invoked).

The implication here is important. Using a nil context when creating a managed object will prevent insertObject: from being called and therefore prevent awakeFromInsert from being called. Consequently, any object initialization or setting of default property values done in awakeFromInsert will not happen automatically when using a nil context.

Bottom line: When using a managed object without a context, awakeFromInsert will not be called automatically and you may need extra code to compensate.

Community
  • 1
  • 1
James Huddleston
  • 8,410
  • 5
  • 34
  • 39
  • Hi this worked for a while until i tried to establish a relationship between my transaction and a category NSManagedObject. then the app crashed because of that. Is there any way around it ? – Edward Ashak Oct 06 '10 at 14:14
  • If you need to set up relationships, I'd go with the two context approach. As tc. points out, objects in different contexts aren't supposed to reference each other. On the other hand, you could postpone setting those relationships until *after* you insert the new unassociated object into your main context. – James Huddleston Oct 06 '10 at 15:44
19

There's a fundamental problem with using a nil MOC: Objects in different MOCs aren't supposed to reference each other — this presumably also applies when one side of a relationship has a nil MOC. What happens if you save? (What happens when another part of your app saves?)

If your object doesn't have relationships, then there are plenty of things you can do (like NSCoding).

You might be able to use -[NSManagedObject isInserted] in NSPredicate (presumably it's YES between inserting and successfully saving). Alternatively, you can use a transient property with the same behaviour (set it to YES in awakeFromInsert and NO in willSave). Both of these may be problematic if a different part of your app saves.

Using a second MOC is how CoreData is "supposed" to be used, though; it handles conflict detection and resolution for you automatically. Of course, you don't want to create a new MOC each time there's a change; it might be vaguely sensible to have one MOC for unsaved changes by the slow "user thread" if you don't mind some parts of the UI seeing unsaved changes in other parts (the overhead of inter-MOC communication is negligible).

tc.
  • 33,468
  • 5
  • 78
  • 96
  • Hi @tc. i tried the first response which is to insertIntoManagedObjectContext:nil but then when i wanted to assign a relationship the app crashed with error: reason: 'Illegal attempt to establish a relationship 'category' between objects in different contexts. so i guess my question is since its not legal to make a relationship between a context NSManaged object and an out of context managed object, what would be the solution ? – Edward Ashak Oct 06 '10 at 14:09
  • I ended up creating a category entity the same way with no context but when it came to saving i added both to the context and then it worked well. – Edward Ashak Oct 06 '10 at 14:31
  • 2
    I can attest to the correctness of this answer. I just got bit by an issue related to having the context nil for an object. Attribute values that are assigned to the object before adding it a context do not propagate to the parent context when eventually the object is added it the child context. Attributes are stored as `nil` in the persistence store. When I switched the order (i.e. assign attribute values after inserting it to a context), things worked properly. Moral of the story is, it's not a good idea to instantiate an object without a context. – Dia Kharrat Mar 21 '14 at 07:06
19

here is how i worked it out:

On load, where we know we are dealing with a new transaction, i created an out of context one.

NSEntityDescription *entity = [NSEntityDescription entityForName:@"Transaction" inManagedObjectContext:self.managedObjectContext];
        transaction = (Transaction *)[[NSManagedObject alloc] initWithEntity:entity insertIntoManagedObjectContext:nil];

then when it came to establishing a relation ship i did this:

if( transaction.managedObjectContext == nil){
        NSEntityDescription *entity = [NSEntityDescription entityForName:@"Category" inManagedObjectContext:self.managedObjectContext];
        Category *category = (Category *)[[NSManagedObject alloc] initWithEntity:entity insertIntoManagedObjectContext:nil];
        category.title = ((Category *)obj).title;
        transaction.category = category;
        [category release];
    }
    else {
        transaction.category = (Category *)obj;
    }

and at the end to save:

if (transaction.managedObjectContext == nil) {
        [self.managedObjectContext insertObject:transaction.category];
        [self.managedObjectContext insertObject:transaction];
    }
    //NSLog(@"\n saving transaction\n%@", self.transaction);

    NSError *error;
    if (![self.managedObjectContext save:&error]) {
        // Update to handle the error appropriately.
        NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
        exit(-1);  // Fail
    }
Edward Ashak
  • 2,411
  • 2
  • 23
  • 38
  • I was wondering if i could do... transaction = (Transaction *)[NSEntityDescription insertNewObjectForEntityForName:@"Transaction" inManagedObjectContext:context]; and then transaction.managedObjectContext = nil; would that be wrong ? – yunas Sep 11 '12 at 09:41
8

You can insert an NSManagedObjectContext with the -[NSManagedObject initWithEntity:insertIntoManagedObjectContext:], passing nil for the managed object context. You must, of course, assign it to a context (using -[NSManageObjectContext insertObject:] before saving. This is, as far as I know, not really the intended pattern in Core Data, however (but see @mzarra's answer here). There are some tricky ordering issues (i.e. making sure the instance gets assigned to a context before it expects to have one, etc.). The more standard pattern is to create a new managed object context and insert your new object into that context. When the user saves, save the context, and handle the NSManagedObjectDidSaveNotification to merge the changes into your 'main' context. If the user cancels the transaction, you just blow away the context and go on with your business.

Community
  • 1
  • 1
Barry Wark
  • 107,306
  • 24
  • 181
  • 206
  • I think you mean `NSManagedObject`, not `NSManagedObjectContext`. Either way, it doesn't look like you can change the MOC a NSManagedObject is associated with — you might be confusing it with `-[NSManagedObjectContext assignObject:toPersistentStore:]`. – tc. Oct 06 '10 at 00:09
2

An NSManagedObject can be created using the nil as the context, but if there other NSManagedObjects it must link to it will result in an error. The way I do it I pass the context into the destination screen and create a NSManagedObject in that screen. Make all the changes link other NSManagedObjects. If the user taps the cancel button I delete the NSManagedObject and save the context. If the user taps the the save button I update the data in the NSManagedObject, save it to the context, and release the screen. In the source screen I update the table with a reload.

Deleting the NSManagedObject in the destination screen gives core data time to update the file. This is usually enough time for you not to see the change in the tableview. In the iPhone Calendar app you have a delay from the time it saves to the time it shows up in the tableview. This could be considered a good thing from a UI stand point that your user will focus on the row that was just added. I hope this helps.

ejkujan
  • 3,541
  • 2
  • 15
  • 4
-2
transaction = (Transaction *)[NSEntityDescription insertNewObjectForEntityForName:@"Transaction" inManagedObjectContext:nil];

if the last param is nil, it will return a NSManagedObject without save to db

user501836
  • 1,106
  • 1
  • 12
  • 20
  • This will cause failure: *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '+entityForName: nil is not a legal NSManagedObjectContext parameter searching for entity name ... – c9s Mar 12 '15 at 12:53