Is there a way to instantiate a NSManagedObject without inserting it? - iphone

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.

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.

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
}

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).

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.

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.

transaction = (Transaction *)[NSEntityDescription insertNewObjectForEntityForName:#"Transaction" inManagedObjectContext:nil];
if the last param is nil, it will return a NSManagedObject without save to db

Related

iOS Core Data when to save context?

I'm having random crashes with core data due to concurrency and multithreading. I know that core data is not thread safe. I also found a couple other answers on how to create a ThreadedDataService and instantiate separate context for each thread.
This is a bit too much for me to swallow at the moment, so I'm trying to find an easier way out.
The solution that I'm trying at the moment is simple: saving data through the main thread. However, now a new issue arises: deadlock. The app becomes unresponsive because each of my insertions of a new NSManagedObject is followed by a call to save. (that's my best guess).
Reading the App Delegate documentation, I noticed that it advises me to save context in applicationWillTerminate.
My question is this: For a long running operation that inserts new events every minute, and the user is not required to see updates propagated to all controllers immediately, when is it a good time for me to save the context?
I'm getting a feeling that saving context for each record may be an overkill?
-(void)insertNewEvent
{
// Create a new instance of the entity managed by the fetched results controller.
NSManagedObjectContext *context = [self.fetchedResultsController.managedObjectContext];
NSEntityDescription *entity = [[self.fetchedResultsControllerfetchRequest] entity];
Event*newManagedObject = (Event*)[NSEntityDescription insertNewObjectForEntityForName:[entity name] inManagedObjectContext:context];
//use accessor methods to set default values
// Save the context. > IS THIS REALLY NEEDED HERE?
NSError *error = nil;
if (![context save:&error])
{
}else
{
if(newManagedObject!=nil)
{
currentState= newManagedObject;
[currentRecord addEvent:newManagedObject];
//Is this call needed?
[self saveApplicationRecords];
}
}
}
I have methods like these defined for all of my managed objects, is it enough if I call such method on a main thread every 10-15 minutes to save pending changes, rather than doing so after each record insertion?
-(void)saveApplicationRecords
{
NSLog(#"saveApplicationRecords");
NSManagedObjectContext *context = [self.applicationRecordsController.managedObjectContext];
// Save the context.
NSError *error = nil;
if (![context save:&error])
{
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
}
}
An extra question after reading macbirdie's response: is this kind of method legal in core data?
-(Event*)insertAndReturnNewEventWithDate:(NSDate*)date_ type:(int)type
{
NSManagedObjectContext *context = [self.dreamEventsController managedObjectContext];
NSEntityDescription *entity = [[self.dreamEventsController fetchRequest] entity];
DreamEvent *newManagedObject = (Event*)[NSEntityDescription insertNewObjectForEntityForName:[entity name] inManagedObjectContext:context];
//handle properties
NSError *error = nil;
if (![context save:&error])
{
return nil;
}else
{
return newManagedObject ;
}
}
Thank you!
You don't have to save the context that early in the process, especially when you want to modify the object afterwards.
In most cases you should create a separate NSManagedObjectContext for the changes you're about to perform on the database. So create objects on it, fill out the properties needed, then send save and perform the whole mergeChangesFromContextDidSaveNotification: trick with the main context (most likely running on the main thread, so using performSelectorOnMainThread... message).
By default an object created and returned by NSManagedObjectContext is autoreleased. If you've created a new object and want to edit it in a form sheet for example, you can call setRetainsRegisteredObjects: with YES to the managed object context before creating the object, so it holds on to the object created until you're done with it. Remember that it's not recommended to manage the NSManagedObjects' lifecycle on your own - you should let the NSManagedObjectContext do it. So, having that in mind, you don't have to retain the NSManagedObject. After the save operation it's unregistered by the context and removed from memory. Nothing is required on your side.
Answer to updated question part
It would be probably better if you returned an NSManagedObjectID (using [object objectID]) instead of the object itself. It allows to safely dispose of the object by the context and if one needs the object for further editing or data retrieval (e.g. from other contexts), they can fetch it from the store separately.
Even if you don't save the context, the newly created object is there and then you can decide if you want to keep the object or not.
After saving on the other hand, if the context still exists, it may return the object with given NSManagedObjectID to you from memory cache - without touching the database.
On yet another hand ;), in your case, you can pretty much safely return the object, since the NSManagedObjectContext creating it still exists, so the object will still exist as well, although already in the autorelease pool.

Core Data: Can't Use Previously Saved Object

I am really stuck with these two things.
What I am trying to do:
My entity is simple. It's a "record".
It has a "name (NSString)" and "parent (relationShip)"
"parent" connect to itself, entity "record".
Ok, now I want to create "parentRecord" and "simpleRecord".
I try to do with that code:
groupRecord = (record *)[NSEntityDescription insertNewObjectForEntityForName:#"record"
inManagedObjectContext:self.managedObjectContext];
groupRecord.name = GroupTextField.text;
[self saveContext];
It's "parentRecord", I save it for a future use, and catch in "groupRecord" variable.
Now I have to create a "simpleRecord". This is a code:
record *newRecord = (record *)[NSEntityDescription
insertNewObjectForEntityForName:#"record"
inManagedObjectContext:self.managedObjectContext];
newRecord.name = textField.text;
[newRecord setMyParent:groupRecord]; //and it crashes here!
I rearranged this code, so *I don't do [self saveContext]; * in "parentRecord".
Just use it from variable groupRecord. And save it in "childRecord" block. Then all is fine. Records save to storage and I can read it from there.
Why does it happens? What should I do, if I want to create "parentRecord" first, SAVE IT ,and later - "childRecord"?
Why can't I use previously saved object? NSManagedObjectContext is the same - what's wrong?
I am good enough with "classic" SQL, but Core Data is killing my brain.
Thanks to everyone.
Update:
Look, saveContext is out of reasons to crash. Here is:
Create parent entity.
Set it to variable of appDelegate.
Save context (for a parent).
Create childEntity.
Set parentProperty from variable of appDelegate. App crashes!
And:
Create parent entity.
Set it to variable of appDelegate.
///////////Save context (for a parent).
Create childEntity.
Set parentProperty from variable of appDelegate. No any crash.
Savecontext this time.
All is fine now.
Parent property - is just a name of the property. It is not some additional setup for a parent in MOM file.
I want to do entity with hierarchy.
And there is NO some additional methods, that Xcode create for me - just a properties.
Okay it sounds like you have a simple data model that looks like this (pseudocode):
Record{
name:string
parent-->Record
}
This is dangerous because there is no reciprocal relationship. This can lead to orphaned objects and compromise the integrity of the object graph. Instead use:
Record{
name:string
parent<--(optional)-->Record.child
child<--(optional)-->Record.parent
}
Now, you have a simple, one dimensional linked list like an array or set. Except for the topmost record object, every record object has a parent and expect for the bottommost object each has a child. To assign one to each you would do:
Record *firstRec; //assuming you have created a custom class for Record
Record *secRec;
firstRec=[NSEntityDescription insertNewObjectForEntityForName:#"Record"
inManagedObjectContext:self.managedObjectContext];
//-------------------------------------^
secRec=[NSEntityDescription insertNewObjectForEntityForName:#"Record"
inManagedObjectContext:self.managedObjectContext];
//-------------------------------------^
firstRec.name=someText;
secRec,name=someOtherText;
firstRec.child=secRec;
[self saveContext];
Now if you want a tree structure in which each parent can have more than one child, you would have an object model like so:
Record{
name:string
parent<--(optional)-->>Record.child
child<<--(optional)-->Record.parent
}
Your insertion and assignments then change to:
Record *firstRec;
Record *secRec;
firstRec=[NSEntityDescription insertNewObjectForEntityForName:#"Record"
inManagedObjectContext:self.managedObjectContext];
//-------------------------------------^
secRec=[NSEntityDescription insertNewObjectForEntityForName:#"Record"
inManagedObjectContext:self.managedObjectContext];
//-------------------------------------^
firstRec.name=someText;
secRec.name=someOtherText;
[firstRec.addChildObject:secRec];
// or
secRec.parent=firsRec;
[self saveContext];
The reason is that a to-many relationship requires a method to add the new object to set. Which cannot be done with a simple assignment. The child, however, only has one parent so it can use a simple assignment. Since the relationship is reciprocal, assigning to one object automatically assigns to the object on the other side of the relationship.
That is how it should work. The errors you are seeing most likely come from having the wrong object model. If you have one-to-one, required relationships like this:
Record{
name:string
parent<--(required)-->Record.child
child<--(required)-->Record.parent
}
... you will encounter problems when you try to save if either a parent or child is missing. Likewise, if you try to assign multiple objects to a to-one relationship, you can get the error you are seeing.
You should never use the cast when doing an insertion because if you have a mismatch between the assigned class and the cast class, the runtime will force the other class into the cast causing all kinds of strange errors.
I can't say for certain exactly what your problem is because I can't see your object model. This however, should point you in the right direction.
Would you share code for "saveContext" and for "setMyParent"?
NSManagedObjectContext has -(BOOL)save:(NSError**)error method. Is that being called within "saveContext"?
And, if your relationship is called "parent", then you should be setting relationship with something like -addParentObject: ... which would be declared in your Record.h file. Xcode will do this for you, if you do things in a certain order. Otherwise, you will need to write the method declarations yourself.

CoreData could not fulfill a fault for when objects are updated by HTTP service

I think I understand the error message: CoreData could not fulfill a fault, but I am not sure how I should deal with it.
We have an application where we use Core Data to persist data returned from a JSON service. Today I am doing the following.
Fetch local object from persistent store and return to UI
Ask server if the object is updated - when I get the answer, I update the Core Data managed object
Update UI with the updated object
The problem is; even if I do not use multi threads I sometimes gets an error when the HTTP request deletes managed objects that my UI has retained. I tried to fetch the objects with returnsObjectsAsFaults to NO. I thought I then could access all the relations and properties of an managed object even if it was deleted (as long as my UI had retained it).
How should I solve this issue?
I thought I could use separate NSManagedObjectContext for read and write. I have made this test:
MyAuthorMO *authorUpdate = [[MyAuthorMO alloc] init]; // I have made this init insert the object into the updateContext
authorUpdate.firstname = #"Hans";
authorUpdate.lastname = #"Wittenberg";
authorUpdate.email = #"Hans#somedomain.no";
NSManagedObjectContext *updateContext = [[MyCoreManager getInstance] managedObjectContext];
NSError *error = nil;
[updateContext save:&error];
NSManagedObjectContext *readContext = [[MyCoreManager getInstance] readOnlyContext];
NSFetchRequest *fetchRequest = [managedObjectModel fetchRequestFromTemplateWithName:#"authorByEmail" substitutionVariables:[NSDictionary dictionaryWithObject:#"Hans#somedomain.no" forKey:#"EMAIL"]];
[fetchRequest setReturnsObjectsAsFaults:NO];
NSArray *authors = [readContext executeFetchRequest:fetchRequest error:&error];
MyAuthorMO * readAuthor = [authors objectAtIndex:0];
// Delete the author with update context:
[updateContext deleteObject:authorUpdate];
[updateContext save:&error];
NSLog(#"Author: %# %#, (%#)", readAuthor.firstname, readAuthor.lastname, readAuthor.email);
The log is outputted just fine as long as I use the readContext for the fetch. If I use the updateContext for the fetch, I get an exception. This looks promising, but I am afraid that I will run into problems at a later stage. Sooner or later I will probably try to access a property that is not fetched completely (a fault). How can I achieve the behaviour I am looking for?
You shouldn't retain managed objects that the context has released. Let the context do that for you.
The problem is that managed objects can exist as either faults or actualized objects. When you retain one, you may retain the fault which contains no data. Even if you do retain the actual object, the object may not behave properly once it has been separated from its context.
In order to handle your scenario, you need a context for the UI and then a context for the server. After either context makes changes, you should merge the context to ensure both are properly updated relative to the store.
Your UI should be configured to reflect the state of data model, you shouldn't have parts of the data model dependent on the state of the UI.
I had the same problem in my database because I refer to object which didnt exist (because I remove it with other relationed object). My solution was to set "No Action" in my relationship.

Model instantiation question when using Core Data

I'm slightly confused in one aspect of Core Data. That is, when do I use the rudimentary alloc/init routine vs created an object with core data and saving it into the current managed object context.
I know that's a rather vague question, so let me give you an example.
I have an application I'm currently working on that iterates through all of a user's contact book on the iPhone. From there, I wrote a model class called 'Person'. I used to do something like this in a loop of people:
Person *person = [[Person alloc] initWithWrapper:mywrapper];
mywrapper would contain an NSDictionary with the attributes for person. Later I'd be able to populate the address book in my app with the person objects.
Now I've started rebuilding parts of the app with Core Data. Do I continue using the strategy above to populate my address book? Or do I do something like this instead:
Person *person = (Person *)[NSEntityDescription insertNewObjectForEntityForName:#"Person" inManagedObjectContext:managedObjectContext];
[person setName:name];
[person setDob:dob];
// Commit the change.
NSError *error;
if (![managedObjectContext save:&error]) {
// Handle the error.
}
The problem is, this code gets executed everytime the app gets started. Should I not be using core data as it will populate the storage mechanism with redundant instances of person everytime the app loads? Should I modify my NSManagedObject (Person class) and add my initWithWrapper: method and continue as I normally would there?
Slightly confused, would love clarification.
You should never be initializing Core Data objects outside of a managed object context - there's simply no point. Having some
Person *person = [[[Person alloc] init] autorelease];
does you no good since you can't save the object, manipulate it, or really do anything useful that Core Data provides without the context (and thus model and store coordinator) backing it up.
You should instead only use the alloc-init combo when you are inserting an object into Core Data for the first time; this is what the initWithEntity:insertIntoManagedObjectContext: method is for. And you're right, every time you call that method you are inserting a new object into the Core Data context and therefore store, and you may wind up with duplicate objects if you're not careful.
What I would instead recommend for you, if you're running code on every startup, is to come up with a Core Data query that returns some set of existing Person objects, and only add objects (using the initialization method) that don't already exist in the store. If the object already exists, modify it instead of creating a new one.
The trick is getting something like this to perform properly. You shouldn't do a Core Data fetch for every contact in the iPhone address book; many small fetches like this are very expensive. You could in theory get two NSSets - one of Person objects, and one of contacts - then compare them by some unique key (like a hash of the first and last names of the contact). I leave the optimization to you.
The key point is this: don't use alloc and init on a Core Data object unless you mean to insert that object for the first time into a context. Instead look at your existing objects and modify them if necessary.
Yeah, it's simplest to add the initWithWrapper method to your Person class. It would be something like this:
- (id) initWithWrapper:(NSDictionary *)wrapper {
NSEntityDescription * person = [NSEntityDescription entityForName:#"Person" inManagedObjectContext:someMOC];
if (self = [super initWithEntity:person insertIntoManagedObjectContext:someMOC]) {
//do your wrapperly initialization here
}
return self;
}
The only downside to this is that this method has to know which managedObjectContext it should insert the object into, so you have to figure out a way to provide that.
That being said, I use this pattern myself all the time.

CoreData: "Dangling reference to an invalid object." error

I'm working on a Cocoa-Touch app, it uses CoreData and has some NSPersistentObject subclasses generated by the XCode model editor.
I've noticed that recently, when saving the context I get an error which has as user info the following part:
(gdb) po ui {
"Dangling reference to an invalid object." = <null>;
NSAffectedObjectsErrorKey = <dump #1 of someObject>;
NSLocalizedDescription = "Operation could not be completed. (Cocoa error 1550.)";
NSValidationErrorKey = <someKey pointing to someObject #1>;
NSValidationErrorObject = <dump #2 of someOtherObject which points to the dump #1 object>;
NSValidationErrorValue = { <list of someMoreObjects> };
}
There are on other keys in the user info dictionary.
All the dumps I get are valid objects, I've verified them all.
NSValidationErrorObject is an object which has an NSSet which contains objects of types dumped in NSAffectedObjectsErrorKey and NSValidationErrorValue. But all these objects are valid.
Furthermore, this happens randomly, sometimes it happens, sometimes not, and sometimes the list dumped in NSValidationErrorValue is longer, sometimes shorter.
I have no clue why this is happening, or what exactly the error is. Does anyone know what's going on? Any ideas what might cause this bizarre error?
This error usually arises because a relationship is set improperly often when an object is left without a necessary reciprocal relationship. The object is "dangling" because the object graph says it should be in a relationship but it is just hanging off in space unconnected to any other object. The object is still valid in the sense that it is internal consistent but it's not in a valid place in the graph.
This question was asked a while back, but I just ran into it. It was not due in my case to a improperly set relationship technically. It was due to the object being set created in a different context, note not on a different thread just a different context on the same thread.
So look for threading issues if you are doing anything with thread with Core Data.
Let's say you have a table "recipes" and a child table "ingredients". You then create a one-to-many relation from recipe's to ingredients and also create an inverse relationship (one-to-one) from ingredients to recipes. It makes sense to specify a delete rule of "cascade" from the recipes table because if you delete a recipe the ingredient should also be deleted. However, if you specify "no action" in the delete rule on the one-to-one relationship in ingredients you will get the dangling reference error when you try to delete an ingredient. Change the delete rule on the one-to-one relationship to "nullify" and this should correct the problem.
I know it's long after the fact, but I've been fighting this problem on a Core Data Model that has ALL relationships set to Nullify. Kept getting these dangling references until I found a single setPrimitiveValue instead of setValue when I was adding to the relationships. Be careful, with relationships, you gotta be sure you do the right thing to let Core Data maintain the relationships for you!
I have another example of how to cause this problem: I have a MOC with a concurrency type of NSMainQueueConcurrencyType. Somewhere in the code I do this:
__block MyObjectType1 *obj1;
[managedObjectContext performBlockAndWait:^{
obj1 = [NSEntityDescription insertNewObjectForEntityForName:#"Thing" inManagedObjectContext:managedObjectContext];
}];
// some other stuff
[self saveContext];
__block NSManagedObjectID *object1ID;
[managedObjectContext performBlockAndWait:^{
object1ID = [obj1 objectID];
}];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// do some slow-ish stuff
[managedObjectContext performBlockAndWait:^{
// create new object that has a relationship
NSManagedObject *obj1_copy = [managedObjectContext objectWithID:object1ID];
MyObjectType2 *obj2 = [NSEntityDescription insertNewObjectForEntityForName:#"OtherThing" inManagedObjectContext:managedObjectContext];
obj2.relatedThing = obj1_copy;
}];
[self saveContext];
});
It turns out that sometimes, this fails. I still don't understand why, but forcing to get a non-temporary objectID seems to do the trick:
[context performBlockAndWait:^{
NSError *error;
[managedObjectContext obtainPermanentIDsForObjects:#[obj1] error:&error];
object1ID = obj1.objectID;
}];
I had the same problem, finally I found the problem was that I was setting a relationship between two different managed object context.
Adding to original answer, there can be couple of reasons for this crash to occur. Read the error description carefully, It In my case i was setting up a relationship with object from another context.
I ran into this issue, and the problem had to do with different (or rather one nil) managed object contexts for entities that had a relationship. In my case, I created a relationship between the entities when both had nil Managed Object Contexts, and then added one of the entities to a MOC, and assumed the other would be added to the MOC as well. I assumed this because of the first two comments to the top answer on this SO thread, but that ended up being wrong.
So lesson learned: if you add an entity to a MOC, other entities that have relationships to it do not get dragged into the MOC along with it. You have to add them to the MOC also or Core Data will not be happy.
My problem was solved using this code:
[[CustomManagedObject managedObjectContext] performBlockAndWait:^{
NSError *error;
if (![[CustomManagedObject managedObjectContext] save:&error])
{
NSLog(#"Error in Saving: %#", [error.userInfo description]);
}
}];