ios - 使用 performBackgroundTask 更新 NSFetchedResultsController

标签 ios swift core-data

我有一个 NSFetchedResultsController,我正在尝试在后台上下文中更新我的数据。例如,这里我试图删除一个对象:

persistentContainer.performBackgroundTask { context in
  let object = context.object(with: restaurant.objectID)
  context.delete(object)
  try? context.save()
}

有两件事我不明白:

  1. 我原以为这会修改父上下文,但不会保存。但是,肯定会保存父上下文(通过手动打开 SQLite 文件验证)。
  2. 我原以为 NSFetchedResultsController 会在后台内容保存回其父级时更新,但这并没有发生。我需要在主线程上手动触发某些东西吗?

显然有些东西我没有得到。谁能解释一下?

我知道我已经正确实现了获取结果 Controller 委托(delegate)方法,因为如果我更改我的代码以直接更新 viewContext,一切都会按预期进行。

最佳答案

解释

NSPersistentContainer 的实例方法 performBackgroundTask(_:)newBackgroundContext() 的文档很少。

无论您调用哪种方法,在任何一种情况下,(返回的)临时 NSManagedObjectContext 都是使用 privateQueueConcurrencyType 设置的,并且与 NSPersistentStoreCoordinator 相关联> 直接,因此没有 parent

参见 documentation :

Invoking this method causes the persistent container to create and return a new NSManagedObjectContext with the concurrencyType set to privateQueueConcurrencyType. This new context will be associated with the NSPersistentStoreCoordinator directly and is set to consume NSManagedObjectContextDidSave broadcasts automatically.

...或者自己确认一下:

persistentContainer.performBackgroundTask { (context) in
    print(context.parent) // nil
    print(context.persistentStoreCoordinator) // Optional(<NSPersistentStoreCoordinator: 0x...>)
}

let context = persistentContainer.newBackgroundContext()
print(context.parent) // nil
print(context.persistentStoreCoordinator) // Optional(<NSPersistentStoreCoordinator: 0x...>)

由于缺少parent,更改不会提交到parent context,例如viewContext 并且 viewContext 未被触及,连接的 NSFetchedResultsController 将无法识别任何更改,因此不会更新或调用其 委托(delegate)的方法。相反,更改将直接推送到 persistent store coordinator,然后保存到 persistent store

希望我能够为您提供帮助,如果您需要进一步的帮助,我可以在我的回答中添加如何获得您所描述的所需行为。 (下面添加了解决方案)

解决方案

您通过使用两个具有父子关系的 NSManagedObjectContext 来实现您所描述的行为:

// Create new context for asynchronous execution with privateQueueConcurrencyType  
let backgroundContext = NSManagedObjectContext(concurrencyType: .privateQueueConcurrencyType)
// Add your viewContext as parent, therefore changes are pushed to the viewContext, instead of the persistent store coordinator
let viewContext = persistentContainer.viewContext
backgroundContext.parent = viewContext
backgroundContext.perform {
    // Do your work...
    let object = backgroundContext.object(with: restaurant.objectID)
    backgroundContext.delete(object)
    // Propagate changes to the viewContext -> fetched results controller will be notified as a consequence
    try? backgroundContext.save()
    viewContext.performAndWait {
        // Save viewContext on the main queue in order to store changes persistently
        try? viewContext.save()
    }
}

但是,您也可以坚持使用 performBackgroundTask(_:) 或使用 newBackgroundContext()。但如前所述,在这种情况下,更改会直接保存到持久存储中,默认情况下不会更新 viewContext。为了将更改向下传播到viewContext,这会导致通知NSFetchedResultsController,您必须设置viewContext.automaticallyMergesChangesFromParenttrue:

// Set automaticallyMergesChangesFromParent to true
persistentContainer.viewContext.automaticallyMergesChangesFromParent = true
persistentContainer.performBackgroundTask { context in
    // Do your work...
    let object = context.object(with: restaurant.objectID)
    context.delete(object)
    // Save changes to persistent store, update viewContext and notify fetched results controller
    try? context.save()
}

请注意,大量更改(例如一次添加 10.000 个对象)可能会使您的 NSFetchedResultsController 发疯,从而阻塞 main queue

关于ios - 使用 performBackgroundTask 更新 NSFetchedResultsController,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/40892147/

相关文章:

ios - 实现Xamarin iOS语音转文本

ios - ObjectMapper - 初始化对象 IOS

ios - EKEvent 的 eventIdentifier 和 calendarItemExternalIdentifier 的区别

ios - 在用户将手指从按钮上移开后,保持显示的发光效果TouchWhen Highlighted

ios - 未找到用于将 Swift 转换为 kotlin 的 Swiftkotlin 命令行

ios - 从 Xcode 项目构建两个输出

ios - 正在调用我的委托(delegate)函数,但我的 UI 仍然没有相应更新

swift - 在核心数据实体中使用枚举类型值

ios - 错误 :'inout IndexPath' 不可转换为 'IndexPath'

ios - 一种临时保留核心数据模型的重复版本的方法