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?
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:
andperformBlockAndWait:
ensure the block operations are executed on the queue specified for the context. TheperformBlock:
method returns immediately and the context executes the block methods on its own thread. With theperformBlockAndWait:
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With