Nathan Gaskin
Nathan Gaskin

Reputation: 1364

Multithreaded Core Data causing UI to hang

I'm trying to execute a fetch against a fairly large data set (~60000 rows) in the background of my app. Despite using a separate thread, the UI noticeably hangs for a second or so whenever the fetch executes. Is my approach correct?

- (id)init
{
    if(self = [super init])
    {
        ABAppDelegate *appDelegate = (ABAppDelegate *)[[UIApplication sharedApplication] delegate];
        _rootManagedObjectContext = appDelegate.managedObjectContext;

        _backgroundContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
        [_backgroundContext setPersistentStoreCoordinator:_rootManagedObjectContext.persistentStoreCoordinator];
    }
    return self;
}


- (void)fetch {
    [_backgroundContext performBlock:^{
        NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:@"ItemPhoto"];
        NSPredicate *pred = [NSPredicate predicateWithFormat:@"full_uploaded_to_server == 0 OR thumb_uploaded_to_server == 0"];
        fetchRequest.predicate = pred;
        NSSortDescriptor *sort = [NSSortDescriptor sortDescriptorWithKey:@"modified" ascending:YES]; //Start at the back of the queue
        fetchRequest.sortDescriptors = [NSArray arrayWithObject:sort];
        fetchRequest.fetchBatchSize = 1;
        fetchRequest.fetchLimit = 1;

        NSError *error;
        NSArray *photos = [_backgroundContext executeFetchRequest:fetchRequest error:&error];
        if(error != nil) {
            NSLog(@"Fetch error: %@", error.localizedDescription);
        }
    }];
}

Looking in Instruments, it's definitely the call to executeFetchRequest: that's taking a long time to complete, and it does seem to be running on its own thread. Why would this be causing the UI to hang?

Thanks!

Upvotes: 0

Views: 653

Answers (1)

Marcus S. Zarra
Marcus S. Zarra

Reputation: 46728

Any activity against the NSPersistentStoreCoordinator is going to cause a block of other activities against it. If you are fetching on one thread and another thread attempts to access the NSPersistentStoreCoordinator it is going to be blocked.

This can be solved in one of two ways:

  1. Reduce the fetches so that the block is not noticeable. Fetching in chunks, for example, can help to reduce this issue.
  2. Make sure the data the UI is exposing is fully loaded into memory. This will stop the main thread from trying to hit the NSPersistentStoreCoordinator and getting blocked.

Depending on the application and the situation, one or the other (or both) of these implementations will remove the issue.

At the end of the day, background threads are not a silver bullet to solve fetch times. If you are planning on fetching on a background thread just to put them onto the main thread you are going to be disappointed with the performance.

If you are fetching on the background thread for a non-UI purpose then consider reducing the size of each fetch or change the batch size, etc. to decrease the fetch time itself.

Update

Warming up the cache doesn't work on iOS like it used to on OS X. You can get the objects fully loaded into memory by configuring your NSFetchRequest so that the objects are fully loaded, relationships are loaded (if needed), your batch size and fetch size are large enough. Naturally this needs to be balanced against memory usage.

Upvotes: 5

Related Questions