开发者

NSFetchedResultsController Delegate and Background driven updates

I ran into NSFetchedResultsController issue developing guest-management application.

The application basically downloads list of guests on background (using NSOperation subclass), inserts them to managed object context and then presents them in table view on UI thread.

I think that I am following the core data multi-threading rules (I have separate MOC for the operat开发者_如何学编程ion created on its thread, I synchronize my main MOC using did-save notification etc.).

What I do not fully understand is the behavior of NSFetchedResultsController which seems to be calling its delegate methods (controllerDidChangeContent etc.) at background thread instead of main thread which leads to illegal UI updates.

So my question is - is it legal to use NSFetchedResultsControllerDelegate to observe changes that come from MOC did save notification or is NSFetchedResultsControllerDelegate designed to work only with changes done on the main thread?

I'm not sure if my explanation is clear enough, if not I can post some code to demonstrate the problem.


My guess is that your MOC did-save notification is being sent and observed on the background thread instead of the main event thread. This would cause the NSFetchedResultsControllerDelegate to send delegate messages on the background thread.

You need to make sure your did-save notification observer passes control to the main thread, e.g:

- (void)backgroundMOCDidSaveNotifiaction:(NSNotification *)notification
{
    [uiMOC performSelectorOnMainThread:
         @selector(mergeChangesFromContextDidSaveNotification:)
     withObject:notification waitUntilDone:NO];
}


Disclaimer

Daniel Dickison response is the right answer. I'm only offering here some extra details and explanations, since some of these steps are not trivial.

Using 2 distinct Managed Object Contexts is the right thing to do

UI Thread MOC:

lazy var mainQueuemanagedObjectContext: NSManagedObjectContext = {
    let coordinator = self.persistentStoreCoordinator
    var managedObjectContext = NSManagedObjectContext(
        concurrencyType: .MainQueueConcurrencyType)
    managedObjectContext.persistentStoreCoordinator = coordinator
    return managedObjectContext
    }()

Transport, Download, Background MOC:

lazy var transportManagedObjectContext:NSManagedObjectContext = {
    let coordinator = CoreDataStack.sharedInstance.persistentStoreCoordinator
    let managedObjectContext = NSManagedObjectContext(
        concurrencyType: .PrivateQueueConcurrencyType)
    managedObjectContext.persistentStoreCoordinator = coordinator
    return managedObjectContext

    }()

Use the background MOC for background operations:

(Such as new data was downloaded and saved)

transportManagedObjectContext.performBlockAndWait({ () -> Void in
    // ...add, change, delete objects, then save
    try transportManagedObjectContext.save()
})

Apply Daniel Dickison' response to the background Managed Object Context, in accordance to the Apple documentation:

// Broadcast NSManagedObjectContextDidSaveNotification
NSNotificationCenter.defaultCenter().addObserver(
    self,
    selector: "mocDidSaveNotification:",
    name: NSManagedObjectContextDidSaveNotification,
    object: self.transportManagedObjectContext)

func mocDidSaveNotification(notification:NSNotification)
{
    mainQueuemanagedObjectContext.performSelectorOnMainThread(
        "mergeChangesFromContextDidSaveNotification:",
        withObject: notification,
        waitUntilDone: true)
}

Note: I generally prefer to use performBlockAndWait() and waitUntilDone: true unless I positively know that not waiting will not cause race conditions. I invite you to thoroughly stress test your application if you decide to not wait. I take the liberty to have the background thread wait for the UI, but never the other way around.

Listen from the UI thread

NSFetchedResultsController must use MainQueueConcurrencyType Managed Object Context.

let fetchedResultsController = NSFetchedResultsController(
    fetchRequest: fetchRequest,
    managedObjectContext: mainQueuemanagedObjectContext,
    sectionNameKeyPath: "yourKey",
    cacheName: nil)

Your NSFetchedResultsController is freed from the background Managed Object Context, and will receive controllerWillChangeContent, didChangeObject, etc. after the merge has been completed.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜