2

I have setup a Core Data app with the usual boilerplate code, and the RootViewController initializes the FRC by calling this:

- (NSFetchedResultsController *)fetchedResultsController 
{
    if (__fetchedResultsController != nil) 
    {
        return __fetchedResultsController;
    }
    // configure the fetchRequest, sectionKey and cacheName
    __fetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest: fetchRequest 
                                                                    managedObjectContext: self.managedObjectContext 
                                                                      sectionNameKeyPath: sectionKey 
                                                                               cacheName: cacheName];
    return __fetchedResultsController;
}

All the sample code I've seen does this. However, I have a large data set and with over 15,000 entries, it takes about 5 seconds to launch the app on the iPhone 4S. This is with caching enabled (without it, it takes 11 seconds), and with indexed attributes.

So I want to be able to show a UIActivityIndicatorView which the app is waiting for this to load. I know how to generally load core data objects in the background thread and then merge them back into the main thread, but how can I initialize the FRC in the background thread so that all the objects are loaded and sectioned in the background?

I know I can load all the objects and partition them in a background thread into a custom dictionary and use that to present the data, but I would rather use the standard FRC calls and delegates.

Thanks.

Lorenzo B
  • 33,216
  • 24
  • 116
  • 190
Z S
  • 7,039
  • 12
  • 53
  • 105

2 Answers2

3

I'm not pretty sure what do you mean with using the NSFetchedResultsController in the background, but based on my experience you could just simply set batch size for your fetch request like the following:

[fetchRequest setFetchBatchSize:20];

In this manner, during startup are loaded the first 20 elements, when you scroll the next 20 and so on. In addition, you could just select the properties to fetch with - (void)setPropertiesToFetch:(NSArray *)values.

Another way is to have a (background) task that starts to fetch objects in background. I think that when fetched in background, the objects are cached in some way (but I'm not pretty sure) and so, you can access them from the main thread more rapidly.

Hope it helps.

Lorenzo B
  • 33,216
  • 24
  • 116
  • 190
  • 1
    The fetchBatchSize helps a little bit, but it doesn't solve my problem (I already have it in my code). I need a sectioned UITableView with the indexes on the side. I'm relying on the FRC to calculate those sections, so no matter what batch size I give it, it needs to read all the rows from the db, find their sections and put the objects in the correct bucket. Now doing this on the main thread stalls the thread for 5 seconds after the app launches, which is not a great experience obviously. – Z S Jun 06 '12 at 18:09
  • For large data sets (iOS 5) all data is fetched if you have a sectionNamekeyPath set regardless of the batch size. – Neil Jun 09 '12 at 16:06
  • @Neil Sorry, but where did you find that reference? – Lorenzo B Jun 09 '12 at 16:30
  • It really helped me a lot. 1 Vote up for that. – Ansari Jan 17 '13 at 22:37
  • One solution for sectionNameKeyPath is to denormalize your data as suggested by Ben Blakely: http://www.cimgf.com/2013/01/03/nsfetchedresultscontroller-sectionnamekeypath-discussion/#more-1994 – chadbag Apr 29 '14 at 23:41
1

I think I've figured it out... you CAN create the FRC in the background thread and do the fetch in the main thread:

- (NSFetchedResultsController *)fetchedResultsController 
{
    if (__fetchedResultsController != nil) 
    {
        return __fetchedResultsController;
    }

    // create something to pass back to the first time FRC is initialized without fetching
    __block NSFetchedResultsController *aFetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:self.managedObjectContext sectionNameKeyPath:nil cacheName:nil];
    aFetchedResultsController.delegate = self;
    [self.list_spinner startAnimating];

    dispatch_async(self.filterMainQueue, ^{

           NSFetchedResultsController *newFetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest: fetchRequest 
                                                                                                         managedObjectContext: self.managedObjectContext 
                                                                                                           sectionNameKeyPath: sectionKey 
                                                                                                                    cacheName: cacheName];
           dispatch_async(dispatch_get_main_queue(), ^{ 
               // stop the spinner here
               [self.list_spinner stopAnimating];

               NSError *error = nil;
               if (![newFetchedResultsController performFetch:&error]) 
               {
                   NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
                   [SimpleListAppDelegate showCoreDataError: @"SimpleListViewController - FRC"];
               }
               __fetchedResultsController = nil;
               newFetchedResultsController.delegate = self;
               __fetchedResultsController = newFetchedResultsController;
               [self.tableView reloadData];
           });
         });

    return aFetchedResultsController;
}
Z S
  • 7,039
  • 12
  • 53
  • 105
  • I am new to the threading concept. But I heard that `NSOperationQueue` can be best alternative to the `NSThread`. The project I am currently working uses the Operation queues. This really works fine by craeating async operations. – hp iOS Coder Jun 08 '12 at 06:08
  • NSOperationQueue is better than using NSThreads, but using GCD (from which dispatch_async comes from) is even better. I would suggest you familiarize yourself with the GCD framework since Apple is going to rely on it more and more in the future. – Z S Jun 08 '12 at 21:16
  • Thanks alot friend, for this important information. But again my question is **why Apple is going to rely on GCD framework more & more.?** _Any specific reason?_ – hp iOS Coder Jun 09 '12 at 05:34
  • 1
    Because a) more efficient since the system doesn't end up creating threads each time, it shares a pool of threads that it manages itself, and b) it's easier for developers to use than using NSThreads directly (once you get your head around it). – Z S Jun 09 '12 at 21:03
  • 1
    In which thread did you create self.managedObjectContext? You use it in different thread, it's a bad idea. – adnako Nov 06 '13 at 13:28