2

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

Nikita Pestrov
  • 5,876
  • 4
  • 31
  • 66

1 Answers1

6

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.

Community
  • 1
  • 1
Lorenzo B
  • 33,216
  • 24
  • 116
  • 190
  • Thank you for the answer, but let me make some clearnes here:1 - i create a new ManagedObjectContext in the new thread, here it is: NSManagedObjectContext *backContext = [[AppDelegate sharedAppDelegate]backManagedObjectContext]; 2-I use the delegate when i create the controller: self.fetchedResultsController = theFetchedResultsController; fetchedResultsController.delegate = self; 3- What's the difference in place of adding the observer?Isn't it thread safe? – Nikita Pestrov Jul 18 '12 at 09:03
  • @NikitaPestrov About the first point, what's `backManagedObjectContext`? I don't see where you create the context. Then, about the third point, I really suggest to register for the save notification in the main thread. Read again Marcus Zarra post to have it clear. Cheers. – Lorenzo B Jul 18 '12 at 09:35
  • @NikitaPestrov A note about the notification. As you can see, the callback notification is valid for context created in background threads. When a notification is arised from those threads, the callback performs the merge operation with the main context. Usually, I register for the save notification (and its callback) in the place where I put the main context. Hope that helps. – Lorenzo B Jul 18 '12 at 09:44
  • Great answer, but here is a resource that 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. http://www.cocoanetics.com/2012/07/multi-context-coredata/ – mrosales Jun 20 '13 at 18:07
  • @mros Thanks! I'll add it to my answer. – Lorenzo B Jun 21 '13 at 10:31