Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Core data background processing still blocks UI

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);
            }];
        }
    }];
like image 966
Oysio Avatar asked Sep 04 '13 14:09

Oysio


People also ask

When should you save context in core data?

Only Save A Context That Has Changes hasChanges : true if you have inserted, deleted or updated the object (a combination of the following properties). isInserted : true when you insert the object into a context.

Should I use Core Data?

The next time you need to store data, you should have a better idea of your options. Core Data is unnecessary for random pieces of unrelated data, but it's a perfect fit for a large, relational data set. The defaults system is ideal for small, random pieces of unrelated data, such as settings or the user's preferences.

What is Nsmanagedobjectcontext in Swift?

An object space to manipulate and track changes to managed objects.

What is core data used for?

Overview. Use Core Data to save your application's permanent data for offline use, to cache temporary data, and to add undo functionality to your app on a single device. To sync data across multiple devices in a single iCloud account, Core Data automatically mirrors your schema to a CloudKit container.


1 Answers

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.

like image 126
Florian Kugler Avatar answered Nov 15 '22 20:11

Florian Kugler