How does deleteObject: work in coreData? - iphone

I just took the plunge and rewrote my App on top of CoreData (previously I was using my own internal save format).
Things are mostly working, although I'm a little confused by the behaviour of deleteObject:.
I have an object that is part of my graph, and when I delete it nothing seems to happen to the object. The object has relationships where some of them are "Cascade" and some are "Nullify". Every relationship to / from the object has an inverse relationship.
After I delete the object, the only thing that seems to change is that the "isDeleted" flag is set on my object. All of the relationships exist as they did before.
If I try to find the objects using a NSFetchRequest, it does not find the deleted objects. However, if I traverse my graph using the KVC relationships, the NSSet returned contains all of the objects including the deleted objects.
After I send the save: method to my ManagedObjectContext, then everything is as I expect.
When I do a deletion, do I need to manually nil out relationships I don't want or do I need to continuously save to keep my data sane? This seems very counter intuitive to me.
Is there anything that I can do to "commit" the deletion or at least make my object graph sane short of doing a save. It seems a little drastic to be doing a save every time I want to modify my graph.
Thanks,
Ron
p.s. Here is some of the behaviour that seems strange to me:
Before deleting the object, this is the "description" of the parent object which has a categoryObjs "to many" relationship:
categoryObjs = (
"0x613e1a0 <x-coredata://1A1AE9E7-66B1-4F4D-A7AB-07D4504CAE2C/TestCategory/p9>",
"0x613e1b0 <x-coredata://1A1AE9E7-66B1-4F4D-A7AB-07D4504CAE2C/TestCategory/p12>",
"0x613e190 <x-coredata://1A1AE9E7-66B1-4F4D-A7AB-07D4504CAE2C/TestCategory/p7>"
);
After deleting the "p12" object (the middle one above), the state of the relationship does not change when accessed through KVC. If I try to fetch the TestCategory entities, then only two are found.
After a "save:" the p12 object disappears:
categoryObjs = (
"0x613e1a0 <x-coredata://1A1AE9E7-66B1-4F4D-A7AB-07D4504CAE2C/TestCategory/p9>",
"0x613e190 <x-coredata://1A1AE9E7-66B1-4F4D-A7AB-07D4504CAE2C/TestCategory/p7>"
);

Every time you call save:, your Managed Object Context must go back to the store and actually write the changes. This is expensive. Therefore, deleteObject: merely marks the object as deleted, which will actually be applied the next time you save. (Remember that this helps out with undo functionality too, it's just going against the way you want to do things.)
According to the documentation, the isDeleted property just states whether the object is going to be deleted upon the next commit, and sets an isDeleted flag on the object. Additionally, deleteObject: will remove the receiver from the context if it was never committed.
For example (where Objects A and B are NSManagedObject instances):
Create Object A
Save MOC
Delete Object A
Object A has been marked for deletion but is not actually deleted until you perform step 2 again.
Contrast with this:
Create Object B
Delete Object B
Object B is gone, since it was never saved, there is no "marking for deletion". It's simply gone.
Edit:
I'm just curious, are you using an NSFetchedResultsController for your tableview's datasource? It's worth looking in to, if you haven't already.

I think Core Data want to minimize memory & IO usage while deleteObject:, and do all the heaver jobs, like write sqllite file, in save:. That could be the most time-efficient way.

Related

CoreData Relationship Saving

I am having troubles with the relationship I have setup in CoreData. Its one to many, a table can have many people but only one person can sit at one table.
My app is based off Apples CoreDataBooks example here.
This does't handle relationships though. You see the 'EditingViewController.m' in the example project, I have this as well, its where the user edits and saves a change to a chosen attribute of a CoreData object.
So let's say they go and choose to edit the name attribute of my person entity. They get a text field where they enter a name and then tap the save button, done.
Now my person entity has a relationship with my table entity, but I can't get it saving correctly and it's likely that I don't fully understand how it works yet. So let's say they select to edit the table of that person, they get a UIPickerView howling a list of tables, great, I've gotten this far, I've used a fetch request to get the table objects and list them.
Normally setting a standard attribute, a line such as this would be called in my code:
[editedObject setValue:textField.text forKey:editedFieldKey];
However, this time I am using a relationship so this won't work, right?
So now my relationship is setup, I try this instead of the above:
[tableObject addGuestObject:(Person *)editedObject];
So from the fetch request that I filled my picker view with I work out the selected table and get that object, tableObject. Then as the CoreDataBooks example did for me already the editedObject has been passed down to this view and is the person in question that we are editing.
So this is where I am liking misunderstanding it. Surely as with the last line of code, we simply take the existing table object (that was selected) and then add the existing guest object to it?
But this gives me this error and crashes:
'NSObjectInaccessibleException', reason: 'CoreData could not fulfill a fault for '0x145640 <x-coredata://2FAD62C1-382A-4398-A4CA-02B4B41DC9A5/Table/p2>''
Not entirely sure what to do to remedy this.
Generally the 'could not fulfil fault' means that the object was deleted out from the persistent store yet you still have a reference to the object. That could happen for a number of reasons (persistent store changed, object was deleted by another thread, object was owned by another object with a relationship which had cascade and it was deleted). Think we need a bit more info about the relationships from it.

"On duplicate key update" for core-data

I would like to know if there is some kind of similar functionality or way to preform an "on duplicate key update" function with core-data as there is with MySQL.
What I want to do is to save an object to the database every time a user presses a button. But if the button is already pressed I want to update the row with some new values instead of adding a new row.
The only way I currently know how to do this is to read the rows from the DB, see if the row exists and then update it.. otherwise add a new row. This seems kind of bad to do this way, am I wrong?
The easiest answer to this is to run a query against the Core Data context and get the object back if it exists. This is the most efficient and least error prone solution to the problem.
You do not need to create a separate NSManagedObjectContext and attempt to deal with merge policies, that is a very inefficient and dangerous way to try and resolve such a simple issue.
Core Data handles a lot of caching for you in the background. If you are attempting to retrieve an object that you just created there is a very high probability that it is still sitting in the cache so the response to your query will be nearly instantaneous.
Note
I just went back to both of those sample projects again to file a bug against them and noticed that they have been updated and finally removed the suggestion of creating a new context. They are now using the NSUndoManager where appropriate and a single context.
EDIT
Remember that the core data framework manages persistence of your object graph. It is not an interface to a sqlite database.
Worry about your object life cycle. When do instances get created? When are they destroyed? What makes your instances unique? Using Books as an example entity, a book has an ISBN which is a unique way of identifying a title, yet many copies of each title can exist. You have two choices in your Entity model, you can create separate instances for each copy of the title or have one instance with a count attribute.
The sample projects CoreDataBooks and iPhoneCoreDataRecipes use NSUndoManager to track state changes between views.

Core Data performance deleteObject and save managed object context

I am trying to figure out the best way to bulk delete objects inside of my Core Data database.
I have some objects with a parent/child relationship. At times I need to "refresh" the parent object by clearing out all of the existing children objects and adding new ones to Core Data. The 'delete all' portion of this operation is where I am running into trouble.
I accomplish this by looping through the children and calling deleteObject for each one.
I have noticed that after the NSManagedObjectContext:Save call following all of the deleteObject calls is very slow when I am deleting 15,000 objects.
How can I speed up this call? Are there things happening during the save operation that I can be aware of and avoid by setting parameters different or setting up my model another way? I've noticed that memory spikes during this operation as well. I really just want to "delete * from".
Thanks.
Supposing that you have in your Core Data model a parent and a child entities, and the parent has a to-many relationship to child called children, you should be able to delete all of the child objects without looping as follows:
NSManagedObject *parentObject = ...;
[parentObject setValue:nil forKey:#"children"];
or using the Core Data generated method
- (void)removeChildren:(NSSet *)value;
NSSet *children = [parentObject valueForKey:#"children"];
[parentObject removeChildren:children];
I am not sure if this will speed up the NSManagedObjectContext save operation. Please let me know about the performances.
Check the relationship dependency graph: a cascade of deletes triggered by the initial deletion will slow things down. If those deletes are unnecessary then change the deletion rule.
according to apple website : http://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/CoreData/Articles/cdCreateMOs.html
it is very simple :
Deleting a managed object is straightforward. You simply send its managed object context a deleteObject: message, passing the object you want to delete as the argument.
[aContext deleteObject:aManagedObject];
This removes the managed object from the object graph. Just as a new object is not saved to the store until the context is saved, a deleted object is not removed from the store until the context is saved.

NSManagedObject for temporary use, how to switch between NSObject and NSManagedObject

Im using a Core Data model for my iPhone app. I have been looking for a way to instantiate or use an Entity outside the ManagedObjectContext. (This should not be done, I know, Im also more looking for a way to not do that, but get the benefits anyway).
My challenge is that I have a view where the user can search for "Persons", all the search results are parsed and put into a Person managedObject then displayed in a list.
If the user clicks a Person from the list, then and only then would I like the Person entity to be persisted to the store, however this requires me to delete all the other results so they don't get persisted along with the desired one. Also to the best of my knowledge, if the user decides to quite the app, the store is persisted, potentially with all current search results mixed in with real user data!
Is there some way I could have a TempPerson NSObject I could use for the search list? Without, however, me having to manually pull the 45 attributes from the temp object and manually set them on the managedObject!
Sort of like:
NSManagedObject aPersonCorrectlyReturnedFromTheStore = (NSManagedObject *)tempPersonOfJustTypeNSObject
I have seen example code from Apple where they build a temporary store to facilitate undo/redo and other stuff on an object that is not yet persisted. This I feel would be overkill in my situation. I just need to display search results until the user selects a Person to persist.
Hope it is clear what Im trying to do, feeling like my Core Data vocabulary isn't quite large enough yet:)
Thanks for any suggestions.
You could create each temporary person object as an NSDictionary or NSMutableDictionary. You can then create a new Person managed object and use the fact that NSManagedObject instances are KVC compliant and use setValuesForKeysWithDictionary:.
New managed objects that are inserted are not actually persisted until you send the managed object context a save: message.
Keep track of them in a collection (set or array) -- you are probably already doing this since you are presenting the search results somehow. Then, delete (deleteObject:) them all except for the one(s) that the user selects.
The deleted managed objects will never be stored.

Core Data not saving a relationship that has been deleted

I have a "to->many" relationship in Core Data with no inverse relationship and the delete rule set to both "Nullify" and "No action" (by that I mean I've tried both with no avail)
Basically I have a MergedStation whose property subStations points to many Station objects: MergedStation.subStation -->> Station
When I call [mergedStation addSubStationsObject:newStation]; (which is dynamically created) everything works great and a new station is added, everything is refreshed and saved correctly so that the next time I open the program, all is right.
However, when I call [mergedStation removeSubStationsObject:stationToRemove]; (also dynamically created), everything deletes correctly only for the current program session. If I close the program and open it again, it's like I didn't change anything. I have the exact same methods called after both the add and remove methods:
[self.managedObjectContext refreshObject:station mergeChanges:YES];
[self.managedObjectContext processPendingChanges];
[self.managedObjectContext saveAndPrintErrors];
I have also tried different combinations of that above code to get it to work. Any help is appreciated!
Joe
It's unclear what you mean by "deletes correctly only for the current program session". The delete rule specifies what should happen when the relationship origin (the MergedStation) is deleted, not what happens when the relationship is broken. Removing the association should not delete any entity instance. If you want to delete the sub station, you must do that manually with -[NSManagedObjectContext deleteObject:].
On a side note, unless you have literally millions of substations (or billions on OS X), you should include the inverse relationship in your managed object model. Core Data is an object graph management framework and object graph management works best (is easiest) when all relationships are bidirectional. Core Data will do a lot of heavy lifting for your if you include the inverse. There's no need for you to make use of the inverse in your code; you can ignore it if you want. The only penalty you pay is a little memory. Until you can prove that extra memory use is detrimental to your code, you should keep the inverse relationship.
I have this same problem. What he means by "deletes correctly only for the current program sessions" is that if you examine the managed object before restarting the application it is in the state you want it to be in (in his case the SubStation object has been removed from the relationship collection).
I added the inverse relationship to the data model as suggested and everything worked magically. Nice to have working, but I would really like to know if there is a way around this without paying the extra memory penalty.
Ugh. I've spent too much time on a problem closely related to this. In my case, I was adding entities to a to-many relationship and losing those changes after relaunching my app. Adding the inverse relationship is what solved the problem, but I don't understand why. I mean, I see why inverse relationships are a good thing, but they should be mandated or at least default by Core Data if a one-way relationship is as useless as it seems to be.
Thanks to Erik P above for giving me the clue I needed! Add those inverse relationships!