Before anyone suggests, we are using one managedObjectContext for [context save:&error];
We are saving a new object in Core Data with iOS5.
The issue is, the first time we save a new object, NSManagingContextDidSaveChangesNotification fires and the [[notification userInfo] objectForKey:NSUpdatedObjectsKey] gives back an unpopulated object. In iOS6, objectForKey:NSUpdatedObjectsKey is populated.
Afterwards when we save objectForKey:NSUpdatedObjectsKey is populated.
The context we are always using is [[UIApplication sharedApplication] delegate].mainDocument.managedObjectContext.
So the issue is, the first time [context save:&error] is called, nothing is saved to the db. But it has been saved to the object, so the next time [context save:&error] is called it gets put in the db.
Does anyone have any info on this. I've heard tell of bugs in CoreData in iOS5 , so if anyone has any advice we would appreciate it.
Check these items in the given order:
Make sure the object is already populated when you save it the first time (e.g. NSLog).
Make sure you query the notifications's userInfo properly.
Make sure at this point that the physical database does not contain the proper information (e.g. via sqlite3 command line tool)
Make sure you output the correct object whenever you check if it is populated or not.
Related
[EDIT: simplified version of the question]
mainMOC is the primary managed object context
editorMOC is a managed object context created in editorViewController with an undo manager so the user can edit a single managed object
after editorMOC saves, mainMOC refreshes the updated managed object in the notification handler for NSManagedObjectContextDidSaveNotification
In the save handler, if I use [mainMOC refreshObject:obj mergeChanges:YES] the updates to the object are not reflected in mainMOC post-refresh. If I use [mainMOC refreshObject:obj mergeChanges:NO] the object is invalidated and at the next fault the changes are reflected in the data loaded from the store.
QUESTION: Why would the object not reflect the update when mergeChanges:YES is specified?
[ORIGINAL QUESTION]
I have a core data based app with multiple managed object contexts. The app is complicated and proprietary so I cannot simply share code directly from the app. I have created a simple test app in an attempt to reproduce my issue but the test app doesn't exhibit the problem. I have not been able to find a logical difference between the implementations. I apologize for not posting sample code, I believe I explained the implementation well below. If something is not clear, please ask in the comments and I will do my best to clarify.
Here's my situation. Everything described below is running on the main thread.
The app has a primary managed object context called mainMOC that is accessed on the main thread and used with NSFetchedResultsControllers to display data in various table views.
I have a view controller called EditorViewController that allows editing of an existing object of a particular entity. This view controller creates it's own managed object context called editorMOC using the same persistent store coordinator with an undo manager so changes can be rolled back or saved when dismissing the editor.
EditorViewController is observing the NSManagedObjectContextDidSaveNotification. When this notification occurs, the notification handler calls [_mainMOC mergeChangesFromContextDidSaveNotification:notification] to merge the changes from editorMOC into mainMOC.
The table view controller that uses an NSFetchedResultsController is handling the controller delegate messages.
I have added NSLog output to look at look at what happens in all of the above steps and I have verified the following:
I can see that the object is modified and saved in the editor.
I can see that the NSManagedObjectContextDidSaveNotification is called and that the updated object is included.
I can see that the fetched results controller is receiving the controller:didChangeObject:atIndexPath:forChangeType:newIndexPath: protocol message.
I can see that mainMOC is not reflecting the updates and that the updated object has not been invalidated by mergeChangesFromContextDidSaveNotification:.
If I quit and relaunch the app, the updates were committed
For reference, both my main app and test app implement the above functionality but the test app shows the updates merged correctly and the main app does not.
I am looking for suggestions on what would cause mergeChangesFromContextDidSaveNotification: not to successfully merge and/or invalidate the updated object.
Thanks!
The fetched results controller does not receive the controller:didChangeObject:atIndexPath:forChangeType:newIndexPath: delegate message. Instead it sends it to the delegate in response to a change in the context. If that delegate message is being sent and does contain the proper object, then the main context is aware of the changes.
If so, then your error is most likely in the the code interfacing the fetched results controller and the tableview. Something is preventing the object from appearing correctly.
The issue I had that caused mergeChangesFromContextDidSaveNotification not to work was I created a new NSPersistentStoreCoordinator for each new NSManagedObjectContext. When I shared NSPersistentStoreCoordinator between all MOCs, mergeChangesFromContextDidSaveNotification works perfectly. Also, make sure mergeChangesFromContextDidSaveNotification is called on the thread that owns the MOC.
From the research I did, it is safe to share NSPersistentStoreCoordinator between threads for use with NSManagedObjectContext. NSManagedObjectContext will lock the persistent store as necessary.
One possible answer: you have a third MOC you didn't know was there / forgot about. (This happened to me.)
I had
mainMOC
editorMOC
viewerMOC which came about by accidentally misguided subclassing - it was supposed to be looking at the main MOC, but instead was creating its own and looking at a frozen state. The "checking for edits" relationship was going the other direction, because it was expected to be the "editor" in this scenario.
the notification is correctly invoking the callback, and the data is being merged correctly into the main MOC (which i could tell because the data was correct on relaunch)
note: refreshObject:mergeChanges: is not needed. it's something i tried, too, when it wasn't working, but the merge of the notification should take care of all the objects for you.
- (void)twinStackUpdated:(NSNotification *)notification {
[[self managedObjectContext] mergeChangesFromContextDidSaveNotification:notification];
}
not sure that this was your problem, but it might be someones, so there it is.
In an effort to accept something even though I don't have a definitive solution, here's what I've done to address this and the problem seems to be resolved.
I believe this was a cache issue with NSFetchedResultsController. I've since simplified this code and made sure that each NSFetchedResultsController uses it's own cache. I've been able to remove the call to 'refreshObject' and things seem to be working correctly.
In my iPhone app, I have an NSFetchedResultsController showing User objects in a UITableView. The User Objects have 0-many Log objects. A summary of the Log objects are shown together with their User object in the UITableView.
My app uses a tab bar, so user input in the logging tab require that the user tab's NSFetchedResultsController is triggered to reload.
So far I do it by writing to the User object:
log.user = nil;
log.user = [User current]; // Same as before. Just triggering a reload.
NSError *error = nil;
[myManagedObjectContext save:&error];
This works fine, but seems a bit like a hack.
Is there a better way to do this? Like specifying that changes in the Log object should propagate to the User object too?
[myFetchedResultsController performFetch:&error];
But why would you want to do this? It should only reload when there is new data for it to fetch. If there is new data then a save of the NSManagedObjectContext is the right action.
What is your underlying goal?
Been browsing through the posts similar to this one but none answered my problem.
Like CoreDataBooks, I use a separate MOC for adding and editing items and observe the proper notifications, like so:
- (void)addControllerContextDidSave:(NSNotification*)saveNotification {
NSManagedObjectContext *context = [self.fetchedResultsController managedObjectContext];
// Merging changes causes the fetched results controller to update its results
[context mergeChangesFromContextDidSaveNotification:saveNotification];
NSLog(#"merging changes in rootview");
}
When I do a save, it's observed correctly. Debugging the notification shows the right context, and debugging the context shows the just added item. The log message also prints just fine. But nothing happens. None of the delegate methods of the NSFetchedResultsController fire, nor is the table view updated.
I tried no cache or clearing it. I've tried reloadData on the tableView, but nothing.
When I quit the app and restart, it shows up fine.
UPDATE:
The notification does indeed contain the right context as its object, which contains the correct item, but the userInfo hash has nothing in it:
userInfo = {
inserted = "{(\n)}";
updated = "{(\n)}";
}
So it looks like the notification doesn't have any changes in it, despite having the correct context with the changed item.... hmmmm.
And the award for dumbest mistake goes toooooo ... Christoph!!
The reason the notification was not showing anything was that I had forgotten to take out a save on the separate MOC before calling the delegate that set up the notification and then saved again.
Since the MOC was just saved, the second save had no changes and thus the notification stayed empty. Removing the first save, as I should have done much earlier, did fix the problem.
Any idea why
//should save the object context.
NSError *error;
if (![managedObjectContext save:&error]) {
NSLog(#"SAVE ERROR");
}
when implemented in a view controller(accessed via a drill down tableview) won't properly save the information? I am passing the moc from the beginning (rootview hands off to tableview, tableview recursively passes it to itself until calls uiview) and any additions I do show up in the table if I pop/push the tableview(unfortunately reloadData in viewWillAppear doesn't want to update it).
Should I be passing the managedObject instead and fetching the context from that?
Since I was manually creating the sqlite entries in the code on load, I was also manually deleting it in the persistentStoreCoordinator each time the program started. Unfortunately, I forgot about that aspect.
I'm new to this Core Data business.
I got a UITableViewController hooked up with a NSFetchedResultsController. In viewDidLoad, I fire a request to get necessary data from the server, then use [self.fetchedResultsController performFetch:&error] to update the table view. All works fine until that point.
Now I want to move the data fetching stuff to another thread, so after the app received a NSArray object from the server, it performs the didFinishFetchingItems selector on the main thread. In that selector, I save the NSArray to the Core Data store and have the fetchedResultsController perform a fetch. No data show up, although a NSLog reveals that the data is still there (e.g. [[fetchedResultsController fetchedObjects] count] returns 100). I have to put a [self.tableView reloadData] at the end of the method to refresh the table view manually.
My question is: What have I done wrong? Why did I need to do the table view refreshing manually?
You should not touch your NSFetchedResultsController in a non-main thread, that is not a thread-safe operation.
If you are having a long delay on fetching then you need to do a background fetch using a separate NSManagedObjectContext. If you perform a separate fetch in the background it will load the data into cache and then the NSFetchedResultsController will hit the cache instead of disk, speeding up retrieval on the main thread.
You do not need to refresh anything manually; fetchedResultsController does that for you.
What you need to do is to implement NSFetchedResultsControllerDelegate for some object, and set that object as the delegate for your fetchedresultscontroller.
See this about what you need to implement. If your model is simple, you can pretty much copy-paste that code into your delegate and everything works.
The important thing is to keep both the resultscontroller and other pieces of code working against the same managed object context. This is how the resultscontroller can pick up the changes. But, in Core Data guide, there are some caveats about multithreading, so make sure you have your threading bases covered and then all works.