I have CoreData
and table with related NSFetchedResultsController
. Controller has context, which created in main queue and works readonly. Of course, tableviewcontroller
implement NSFetchedResultsControllerDelegate
protocol.
Take a look at on of the method, that it implements:
- (void)controller:(NSFetchedResultsController *)controller didChangeObject:(id)anObject atIndexPath:(NSIndexPath *)indexPath forChangeType:(NSFetchedResultsChangeType)type newIndexPath:(NSIndexPath *)newIndexPath {
switch (type) {
case NSFetchedResultsChangeInsert:
NSLog(@"Inserted in %@", [NSString stringWithUTF8String:dispatch_queue_get_label(dispatch_get_current_queue())]);
[_tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath] withRowAnimation:UITableViewRowAnimationFade];
break;
case NSFetchedResultsChangeDelete:
[_tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
break;
case NSFetchedResultsChangeUpdate:
NSLog(@"Updated in %@", [NSString stringWithUTF8String:dispatch_queue_get_label(dispatch_get_current_queue())]);
[_tableView reloadRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
break;
case NSFetchedResultsChangeMove:
[_tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
[_tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath] withRowAnimation:UITableViewRowAnimationFade];
break;
}
}
In background I download data and update database in my application. Updating of database always the same. It is updated in method of my data manager:
- (void)saveDataInBackgroundInForeignContext:(void (^)(NSManagedObjectContext *))saveBlock completion:(void (^)(void))completion {
AppDelegate *delegate = (AppDelegate *)[[UIApplication sharedApplication] delegate];
dispatch_async([delegate.dispatcher queueForDataSavingInModel:self.modelName], ^{
[self saveDataInForeignContext:saveBlock];
dispatch_sync(dispatch_get_main_queue(), ^{
completion();
});
});
}
- (void)saveDataInForeignContext:(void (^)(NSManagedObjectContext *))saveBlock {
NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator];
if (coordinator != nil) {
NSManagedObjectContext *localContext = [[NSManagedObjectContext alloc] init];
[localContext setPersistentStoreCoordinator:coordinator];
[localContext setMergePolicy:NSMergeByPropertyObjectTrumpMergePolicy];
[self.managedObjectContext setMergePolicy:NSMergeByPropertyStoreTrumpMergePolicy];
[[NSNotificationCenter defaultCenter] addObserver:self.managedObjectContext
selector:@selector(mergeChangesFromContextDidSaveNotification:)
name:NSManagedObjectContextDidSaveNotification
object:localContext];
saveBlock(localContext);
if (localContext.hasChanges) {
[self updateLastUpdateDateInConformedUpdatedObjects:localContext];
NSError *error = nil;
BOOL success = [localContext save:&error];
if (!success) {
NSLog(@"Saving in foreign context failed. %@", error.userInfo);
}
}
[localContext release];
}
}
in saveBlock
I modify context depends on data from server.
So, in results I have strange behavior:
Pay attention to NSLog
in controller:didChangeObject:atIndexPath
method in first listing. And lets take a look on logs:
2012-11-16 02:59:33.376 [27824:5303] Inserted in ru.idecide.saving.calls // WTF WHY?!
2012-11-16 03:05:56.219 [27824:c07] Updated in com.apple.main-thread
ru.idecide.saving.calls - queue
of saving data.
This doesn't really matter, everything work, but method insertRowsAtIndexPaths
have effect on UI in 2-3 seconds after calling on inserting and (obviously) immediately on updating. Why does it happen and what can I do to avoid it?
The problem is here:
[[NSNotificationCenter defaultCenter] addObserver:self.managedObjectContext
selector:@selector(mergeChangesFromContextDidSaveNotification:)
name:NSManagedObjectContextDidSaveNotification
object:localContext];
You're linking your main-thread context directly to your background-queue context. When localContext
, running on your background thread, posts a notification, the notification is delivered to its observers (self.managedObjectContext
) on the same queue - the background queue.
You need transfer the notification to the main thread before delivering it to self.managedObjectContext
. Give yourself a new method to receive the notification on the background queue and forward it to the main thread:
- (void)backgroundContextDidSave:(NSNotification *)note {
dispatch_async(dispatch_get_main_queue(), ^{
[self.managedObjectContext mergeChangesFromContextDidSaveNotification:note];
});
}
Then use that method selector when you register for notifications:
[[NSNotificationCenter defaultCenter] addObserver:self.managedObjectContext
selector:@selector(backgroundContextDidSave:)
name:NSManagedObjectContextDidSaveNotification
object:localContext];