What could cause mergeChangesFromContextDidSaveNotification not to merge/invalidate objects that have been updated?
Asked Answered
W

4

7

[EDIT: simplified version of the question]

  1. mainMOC is the primary managed object context

  2. editorMOC is a managed object context created in editorViewController with an undo manager so the user can edit a single managed object

  3. after editorMOC saves, mainMOC refreshes the updated managed object in the notification handler for NSManagedObjectContextDidSaveNotification

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

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

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

  3. EditorViewController is observing the NSManagedObjectContextDidSaveNotification. When this notification occurs, the notification handler calls [_mainMOC mergeChangesFromContextDidSaveNotification:notification] to merge the changes from editorMOC into mainMOC.

  4. 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!

Whelan answered 9/5, 2011 at 19:9 Comment(1)
Have the same problem... replacing [mainMOC refreshObject:obj mergeChanges:YES] with [mainMOC save:nil] works (like [mainMOC refreshObject:obj mergeChanges:NO]). Don't understand why... BTW: mergePolicy settings have no effects.Widgeon
W
0

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.

Whelan answered 26/9, 2012 at 16:37 Comment(0)
W
2

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.

Wideopen answered 11/5, 2011 at 3:7 Comment(8)
thanks for your answer, I know this is a hard problem to diagnose. Unfortunately this is not the problem. I have verified that when the notification handler calls [_mainMOC mergeChangesFromContextDidSaveNotification:notification] the updated object is not merged correctly into the _mainMOC. I verified this by looking at the object's data after the merge and then after forcefully invalidating and faulting the object. After the merge I see the old (incorrect) data. After the forced invalidation I see the new (correct) data. All of this is independent of NSFetchedResultsController.Whelan
You might try using a diagnostic NSManagedObjectContext subclass and overriding mergeChangesFrom... so that you can capture the notification. (You might be able to trap it in the debugger as well.) Perhaps the notification isn't carrying the info you think it is. You should also check the merge policy on the main context. Do you have any custom insertion validation logic?Wideopen
I checked the info in the notification and it looks right (updated object is present with the correct data). I haven't explicitly set a merge policy so would be default. Doesn't the merge policy only come into play when saving the context? Looking at the docs for refreshObject:mergeChanges there are comments about how the merge is done (load from store/cache, apply merge, reapply local updates) which make sense but I have no local updates. Also, no custom insertion validation logic. I've worked around it by invalidating but I have to be missing something.Whelan
At this point, I got nothing. I will note that using a second context just for undo is rather unusual. I don't see how it would cause this problem, I just mention it as aside.Wideopen
Just a sudden thought, could the undo manager be tangling something up? I can't think of anything but we are looking for something that will cause a reversion to old data.Wideopen
Why is this unusual? Or rather, what would be the "usual" way to do this? I did it this way b/c the data model contains many entities and other things are happening that can change objects within mainMOC. I wanted to isolate the changes made in editorMOC and allow for undo w/o affecting other changes made in mainMOC.Whelan
It is unusual to use multiple context on the same thread/operation. You seem to have a good reason for doing so but I don't see a lot of people doing it that way. That just makes me wonder what could be going on. At this point, I'm grasping at straws.Wideopen
that's how I feel. I'll give you a +1 for the time and ideas.Whelan
A
2

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.

Antigone answered 31/3, 2013 at 19:22 Comment(2)
thanks. my issue was different. I only have one persistentStoreCoordinator.Whelan
Helps me fix an issue with updates under iOS6. Since iOS7 mergeChangesFromContextDidSaveNotification: works perfect with 2 NSPersistentStoreCoordinator as well.Frumpish
A
0

One possible answer: you have a third MOC you didn't know was there / forgot about. (This happened to me.)

I had

  1. mainMOC
  2. editorMOC
  3. 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.
  4. 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.

Avunculate answered 18/6, 2011 at 3:23 Comment(0)
W
0

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.

Whelan answered 26/9, 2012 at 16:37 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.