NSFetchedResultsController 委托和后台驱动的更新

发布于 2024-11-27 15:32:30 字数 494 浏览 1 评论 0原文

我在开发访客管理应用程序时遇到了 NSFetchedResultsController 问题。

该应用程序基本上在后台下载来宾列表(使用 NSOperation 子类),将它们插入到托管对象上下文中,然后将它们呈现在 UI 线程上的表视图中。

我认为我遵循核心数据多线程规则(我有单独的 MOC 用于在其线程上创建的操作,我使用保存通知等同步我的主 MOC)。

我不完全理解的是 NSFetchedResultsController 的行为,它似乎在后台线程而不是主线程调用其委托方法(controllerDidChangeContent 等),这会导致非法的 UI 更新。

所以我的问题是 - 使用 NSFetchedResultsControllerDelegate 观察来自 MOC 保存通知的更改是否合法,还是 NSFetchedResultsControllerDelegate 设计为仅适用于主线程上完成的更改?

我不确定我的解释是否足够清楚,如果不是我可以发布一些代码来演示问题。

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 operation 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.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

生死何惧 2024-12-04 15:32:30

我的猜测是,您的 MOC 保存通知是在后台线程而不是主事件线程上发送和观察的。这将导致 NSFetchedResultsControllerDelegate 在后台线程上发送委托消息。

您需要确保您的保存通知观察者将控制权传递给主线程,例如:

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

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];
}
春风十里 2024-12-04 15:32:30

免责声明

Daniel Dickison 回复正确答案。我在这里仅提供一些额外的细节和解释,因为其中一些步骤并不简单。

使用 2 个不同的托管对象上下文是正确的做法

UI 线程 MOC:

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

传输、下载、后台 MOC:

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

    }()

使用后台 MOC 进行后台操作:

(比如新数据被下载并保存)

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

Daniel Dickison'响应应用于后台托管对象上下文,根据 Apple 文档

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

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

注意:我通常更喜欢使用performBlockAndWait()waitUntilDone: true除非我确信不等待不会导致竞争条件。如果您决定不等待,我邀请您对您的应用程序进行彻底的压力测试。我冒昧地让后台线程等待 UI,但从不相反。

从 UI 线程监听

NSFetchedResultsController 必须使用 MainQueueConcurrencyType 托管对象上下文。

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

您的 NSFetchedResultsController 从后台托管对象上下文中释放,并将接收 controllerWillChangeContentdidChangeObject 等。之后合并已完成。

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.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文