8

For my app, I'm fetching quite a lot objects from the core data store, this causes the app to freeze and blocks all UI input. I would want to do the fetching in the background while the app remains responsive and update the tableview only when the data is available.
For this purpose I've setup a new NSManagedObjectContext with NSPrivateQueueConcurrencyType and made it as a child of the main MOC. While my setup is returning the desired objects it seems like all the processing is still freezing the UI and like there is almost no difference in responsiveness with the old code where everything was happening on the main queue.

According to this article the child contexts setup does not help on keeping the UI responsive while everywhere else on the net I read this is the way to go if you want to relieve the main queue from heavy processing? Am I missing something ?

  NSManagedObjectContext *mainMOC = self.mainObjectContext;
  NSManagedObjectContext *backgroundMOC = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];

    [backgroundMOC setParentContext:mainMOC];
    [backgroundMOC performBlock:^{

        //query for objects
        NSArray *results = [Product MR_findAllInContext:backgroundMOC];

            NSError *childError = nil; 
            [backgroundMOC save:&childError]; 

        if ( [results count] > 0 ) {
            //get objectIDs
            NSMutableArray *objectIDs = [NSMutableArray array]
            for (NSManagedObject *object in results) {
            [objectIDs addObject:[object objectID]];
            }

            [mainMOC performBlock:^{
                //refetch objects on the mainQueue
                NSMutableArray *persons = [NSMutableArray array]
                for (NSManagedObjectID *objectID in objectIDs) {
                [persons addObject:(Person*)[mainMOC objectWithID:objectID]];
                }
                 //return result
                 if (self.callBack)
                 self.callBack(persons);
            }];
        }
    }];
Oysio
  • 3,486
  • 6
  • 45
  • 54
  • 1
    I suggest you watch the WWDC 2013 video "Core Data Performance Optimization and Debugging" which talks about this stuff. – Mike Weller Sep 04 '13 at 15:06
  • Don't konw if that's the case, there was a bug on iOS 5 about nested context freezing the UI where the main context was backed by an NSFetchedResutsController, see this [Core Data nested managed object contexts and frequent deadlocks-freezes](http://stackoverflow.com/questions/11786436/core-data-nested-managed-object-contexts-and-frequent-deadlocks-freezes) – Leonardo Sep 04 '13 at 15:30
  • if your code is invoked on the main thread using a child context and a performBlock is useless, please be sure your pasted code isn't called from the main thread – Jerome Diaz Sep 04 '13 at 15:48
  • any solution to this? I am getting the same error – Kong Hantrakool Jan 22 '16 at 20:45

1 Answers1

19

First of all, it would be helpful to know what MR_findAllInContext: does exactly. The best solution would be to solve this in a more efficient way. How does the predicate look like? Do you specify a batch size on the request? Do yo use indexes on attributes you're querying for? What's the size of your data set? It's hard to tell if there is a better solution without more details.

You're current approach suffers from what seems to be a pretty widespread misunderstanding of how nested contexts work.

The problem is the way the contexts are setup. Since you make the background context the child of the main context, everything you do in the background context has to go "through" the main context.

Saving the background context will result in pushing all changes in the object graph to the main context which then has to save as well to persist the changes. Doing a fetch request on the background context will forward it to the main context, which will send it to the persistent store coordinator and hands back the results to the background context synchronously. Any request on the background context (fetch or save) will lock the parent context and also block the main thread similarly to when you do the request directly on the main context.

Adding a background context behind a main thread context is not going to fly performance wise. Nested contexts are just not made to be used in this way.

In order to achieve what you want, you would have to perform the fetch request on a context which is independent from the main context, e.g. a background context which is directly associated with the PSC. In this case the fetch request will still lock the PSC. This means that executing a request on the main context during this time will still block the main thread, because of the lock contention on the PSC. But at least the main thread will not be blocked in general.

Be aware though that when you hand the resulting objectIDs to the main context, get the objects there with objectWithID: and then access these objects, you're relying on the PSC's row cache to still hold the data in order for this to be fast. Since the objects will be faults at first, Core Data would have to go to disk for every object if the row cache doesn't have the data anymore. This will be very slow. You can check with Instruments for cache hits and misses.

Florian Kugler
  • 688
  • 6
  • 10