Search code examples
iosobjective-ccore-dataios8nsmanagedobjectcontext

Core Data update locations for positions in background cause blocking UI


I am using 3 Managed Object Contexts Architecture (creating temporaryContext for background which parent is managedObjectContext - UI, and which has parent writerObjectContext which should write to database in background) and I have a problem with blocking UI when I updating objects. Example would be best. So I have thousands of points in my database and I am using NSFetchedResultsController with tableView for getting them. Here is my code:

- (void)viewDidLoad
{
    [super viewDidLoad];

    temporaryContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
    temporaryContext.parentContext = [[CoreDataManager manager] managedObjectContext];
    temporaryContext.undoManager = nil;

    ...
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{   
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:PositionCellIdentifier forIndexPath:indexPath];
    [self configureCell:(PanelPositionCell *)cell atIndexPath:indexPath];
    return cell;
}

- (void)configureCell:(PanelPositionCell *)cell atIndexPath:(NSIndexPath *)indexPath {
    // Fetch Record
    NSManagedObject *record = [self.fetchedResultsController objectAtIndexPath:indexPath];
    OpenPositionCD *position = (OpenPositionCD *)record;

    // Update Cell
    [cell setValuesByOpenPositionCD:position];

    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
        [self checkAddress:position];
    });
}

- (void)checkAddress:(OpenPositionCD *)openPosition {
    if (openPosition.latitude == 0 && openPosition.longitude == 0) {
        return;
    }

    if ([openPosition hasAddress]) {
        return;
    }

    CLLocation *location = [[CLLocation alloc]initWithLatitude:[openPosition.latitude doubleValue] longitude:[openPosition.longitude doubleValue]];
    [[LocationManager manager] getPlacemarksForLocation:location withCompletion:^(NSArray *placemarks, NSError *error) {
        if (!error) {
                openPosition.address = placemarks[0];
                            NSError *error = nil;
                if (![temporaryContext save:&error]) {
                    NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
                }
        }
    }];
}

When I am scrolling to cells which don't have addresses the UI is freeze often which depends on how fast I am scrolling. So how can I fix it? I was trying with/without dispatch_async and with/without temporaryContext performBlock but looks nothing can help me. So thanks for any help.

I am adding initializing of contexts in CoreDataManager but I hope it's allright:

// Returns the managed object context for the application.
// If the context doesn't already exist, it is created and bound to the persistent store coordinator for the application.
- (NSManagedObjectContext *)managedObjectContext{
    if (_managedObjectContext != nil) {
        return _managedObjectContext;
    }

    _managedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
    _managedObjectContext.parentContext = [self writerManagedObjectContext];

    return _managedObjectContext;
}

// Writer context for database
- (NSManagedObjectContext *)writerManagedObjectContext{
    if (_writerManagedObjectContext != nil) {
        return _writerManagedObjectContext;
    }

    NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator];
    if (coordinator != nil) {
        _writerManagedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
        [_writerManagedObjectContext setPersistentStoreCoordinator:coordinator];
    }
    return _writerManagedObjectContext;
}

Solution

  • You are using outdated APIs. The recommended way to use multiple contexts is not to assign the same persistent store coordinator to the child context, but instead assign it a parentContext.

    You probably want M. Zarra's setup with

    WriterContext (background)
    MainContext (main thread, parent is WriterContext)
    WorkerContext (background, parent is MainContext, create and destroy as needed)
    

    You would do the background work in a worker context and save which would push the changes to the main context. You can save the main context when it is convenient, and the data store only gets hit in the background when the writer context is saving.

    Finally, you are using the position object in a different thread. You need to wrap your calls into a worker context's performBlock block to safely use these objects.