Search code examples
objective-cioscore-datansfetchedresultscontrollernsmanagedobjectcontext

FetchedResultsController doesn't see the changes in managedObjectContext after data import


I'm working on the data import part in my app, and to make the UI more reliable, i followed this Marcus Zarra article http://www.cimgf.com/2011/08/22/importing-and-displaying-large-data-sets-in-core-data/

The idea is that you make the import in a separate context in the background tread(i use GCD for that), and your fetchedResultsController's context merges the changes by observing the NSManagedObjectContextDidSaveNotification.

The issue i get is very strange to me - my fetchedResultsController doesn't get those changes itsef and doesn't reload the TableView when the new data comes. But if i fire the following method, which makes the fetch and reloads the table - it gets it all there.

- (void)updateUI
{                                         
  NSError *error;
  if (![[self fetchedResultsController] performFetch:&error]) {
    NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
  }
  [self.tableView reloadData];
}

So now i call that method when i get the NSManagedObjectContextDidSaveNotification to make it work, but it looks strange and nasty to me.

 - (void)contextChanged:(NSNotification*)notification
{
  if ([notification object] == [self managedObjectContext]) return;

  if (![NSThread isMainThread]) {
    [self performSelectorOnMainThread:@selector(contextChanged:) withObject:notification waitUntilDone:NO];
    return;
  }

  [[self managedObjectContext] mergeChangesFromContextDidSaveNotification:notification];
  //TODO:Make it work as it should - merge, without updateUI
   [self updateUI];//!!!Want to get rid of this!
}

Why can it be like this? Here is the code that is responsible for parsing the data and adding the Observer.

- (void)parseWordsFromServer:(NSNotification *)notification
{

  dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0) , ^{
    NSDictionary *listInJSON = [notification userInfo];
    wordsNumbers = [[listInJSON valueForKey:@"words"]mutableCopy];

    if ([wordsNumbers count])
    {
      [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(contextChanged:) name:NSManagedObjectContextDidSaveNotification object:nil];

//New Context for the new thread NSManagedObjectContext *backContext = [[AppDelegate sharedAppDelegate]backManagedObjectContext];

      //Get all the words we already have on this device
      NSArray *wordsWeHave = [Word wordsWithNumbers:wordsNumbers inManagedContext:backContext];
      //Add them to this list
      for (Word *word in wordsWeHave)
        [[List listWithID:[currentList listID] inManagedObjectContext:backContext]addWordsObject:word];
      [backContext save:nil];!//Save the context - get the notification
         }

  });

}

EDIT I use the NSFetchedResutsControllerDelegate, indeed, how else could i pretend my tableview to be updated if i didn't?

UPDATE Decided just to move to Parent - Child paradigm


Solution

  • The problem has been discussed many times, like NSFetchedResultsController doesn't show updates from a different context, and it's quite difficult to understand what is going on but I have few notes.

    First, you are violating a simple rule: you need to have a managed object context per thread (Concurrency with Core Data Section).

    Create a separate managed object context for each thread and share a single persistent store coordinator.

    So, inside your custom thread access the main context, grab its persistent coordinator and set it to the new context.

    NSManagedObjectContext *moc = [[NSManagedObjectContext alloc] init];
    [moc setPersistentStoreCoordinator:persistentStoreCoordinatorGrabbedFromAppDelegate];
    

    Second, you don't need to register

    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(contextChanged:) name:NSManagedObjectContextDidSaveNotification object:nil];
    

    within the new thread. Just register for it within the class that create the new thread (or in the app delegate).

    Finally, if you are not using a NSFetchedResutsControllerDelegate, use it. It allows to get rid of reloading data table. When the context changes, the delegate responds to changes: edit, remove, add.

    Starting from iOS 5, you could just use new Core Data API and make your life easier with new confinement mechanism.

    Edit

    From @mros comment.

    Multi-Context CoreData

    It may help you understand a little bit more about the advantages of using a parent-child core data model. I particularly like the bit about using a private queue context to handle the persistent store. Make sure to read down through the whole thing because the beginning shows how not to do it.

    Hope that helps.