Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

NSManagedObjectContext: performBlockAndWait vs performBlock with notification center

I came across intriguing behaviour when using NSManagedObjectContext's performBlock: with notification center.

From the main UI thread I trigger asynchronous data download (using NSURLConnection's connectionWithRequest:). When data arrive the following delegate method is called:

- (void)downloadCompleted:(NSData *)data
{
    NSArray *new_data = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil];

    self.backgroundObjectContext = [[NSManagedObjectContext alloc]   
                          initWithConcurrencyType:NSPrivateQueueConcurrencyType];
    self.backgroundObjectContext.persistentStoreCoordinator = self.persistentStoreCoordinator;

    [self.backgroundObjectContext performBlockAndWait:^{
        [self saveToCoreData:new_data];
    }];
}

The savetoCoreData: method is simply saving new data to the background context:

- (void)saveToCoreData:(NSArray*)questionsArray
{
    for (NSDictionary *questionDictionaryObject in questionsArray) {
        Question *newQuestion = [NSEntityDescription 
                      insertNewObjectForEntityForName:@"Question" 
                               inManagedObjectContext:self.backgroundObjectContext];

        newQuestion.content = [questionDictionaryObject objectForKey:@"content"];            
    }

    NSError *savingError = nil;
    [self.backgroundObjectContext save:&savingError];
}

In the view controller, in viewDidLoad I add observer to the notification center:

[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(contextChanged:) 
                                             name:NSManagedObjectContextDidSaveNotification
                                           object:nil];

And then in the contexChanged: I merge the background context with the main context so that my NSFetchedResultsController's delegate methods are called where my view can be updated:

- (void)contextChanged:(NSNotification*)notification
{
    if ([notification object] == self.managedObjectContext) return;

    [self.managedObjectContext mergeChangesFromContextDidSaveNotification:notification];
}

It all seems to work well, but there is one thing that bothers me. When in downloadCompleted: method I use performBlock: instead of performBlockAndWait: the notification seems to be delayed. It takes noticeable (around 5s) amount of time from the moment the background thread does save: till the moment NSFetchedResultsController calls its delegate. When I use performBlockAndWait: I do not observe any visible delay - the delegate is called as fast as if I called saveToCoreData: inside _dispatch_async_.

Does anyone saw that before and know if this is normal or am I abusing something?

like image 773
Marcin Czenko Avatar asked Nov 01 '22 07:11

Marcin Czenko


1 Answers

As pointed out by Dan in one of the comments, the merge operation should happen on the main thread. This can be easily observed by changing the contextChanged: method to do the following:

 - (void)contextChanged:(NSNotification*)notification
{
    if ([notification object] == self.managedObjectContext) return;

    if (![NSThread isMainThread]) {
        [self performSelectorOnMainThread:@selector(contextChanged:) 
                               withObject:notification 
                            waitUntilDone:YES];
        return;
    }

    [self.managedObjectContext mergeChangesFromContextDidSaveNotification:notification];
}

With this change, both performBlock: and performBlockAndWait: are working.

As long as this explains to some extent why the problems were occurring in the first place, I still do not understand why performBlock: and performBlockAndWait: perform differently from the threading perspective. Apple documentation says:

performBlock: and performBlockAndWait: ensure the block operations are executed on the queue specified for the context. The performBlock: method returns immediately and the context executes the block methods on its own thread. With the performBlockAndWait: method, the context still executes the block methods on its own thread, but the method doesn’t return until the block is executed.

This indicates, that if the true root cause of the problem described in the question is that merging is happening in the background thread, then I should observe identical behaviour regardless of which method I am calling: performBlock: and performBlockAndWait: - both are executing in a sperate thread.

As a side note, since the NSURLConnection calls the delegate method on the same thread that started the asynchronous load operation - main thread in my case - using background context seems to be not necessary at all. NSURLConnections deliver its events in the run loop anyway.

like image 193
Marcin Czenko Avatar answered Nov 15 '22 05:11

Marcin Czenko