CoreData 中的异步读取 - 使用 newBackgroundContext + FetchRequest 与 newBackgroundContext + NSAsynchronousFetchRequest 的区别?

Asynchronous read in CoreData - Difference in using newBackgroundContext + FetchRequest vs newBackgroundContext + NSAsynchronousFetchRequest?

似乎有两种方法可以在不阻塞主线程的情况下在 CoreData 中执行异步读取 UI。


newBackgroundContext + NSFetchRequest

来源:https://www.advancedswift.com/core-data-background-fetch-save-create/

// Create a new background managed object context
let context = persistentContainer.newBackgroundContext()

// If needed, ensure the background context stays
// up to date with changes from the parent
context.automaticallyMergesChangesFromParent = true

// Perform operations on the background context
// asynchronously
context.perform {
    do {
        // Create a fetch request
        let fetchRequest: NSFetchRequest<CustomEntity>

        fetchRequest = CustomEntity.fetchRequest()
        fetchRequest.fetchLimit = 1

        let objects = try context.fetch(fetchRequest)

        // Handle fetched objects
    }
    catch let error {
        // Handle error
    }
}

newBackgroundContext + NSAsynchronousFetchRequest

来源:https://www.marcosantadev.com/coredata_crud_concurrency_swift_2/

let privateManagedObjectContext = persistentContainer.newBackgroundContext()

// Creates a fetch request to get all the dogs saved
let fetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: "Dog")

// Creates `asynchronousFetchRequest` with the fetch request and the completion closure
let asynchronousFetchRequest = NSAsynchronousFetchRequest(fetchRequest: fetchRequest) { asynchronousFetchResult in

    // Retrieves an array of dogs from the fetch result `finalResult`
    guard let result = asynchronousFetchResult.finalResult as? [Dog] else { return }

    // Dispatches to use the data in the main queue
    DispatchQueue.main.async {
        // Do something
    }
}

do {
    // Executes `asynchronousFetchRequest`
    try privateManagedObjectContext.execute(asynchronousFetchRequest)
} catch let error {
    print("NSAsynchronousFetchRequest error: \(error)")
}

但是,请注意,如果我要启用标志 -com.apple.CoreData.ConcurrencyDebug 1,上面的代码将很不幸地导致致命错误。到目前为止,我还没有很好的解决方案。详情请参考Why I am getting Multithreading_Violation_AllThatIsLeftToUsIsHonor for this simplest NSAsynchronousFetchRequest use case?


请问newBackgroundContext + NSFetchRequestnewBackgroundContext + NSAsynchronousFetchRequest有什么区别?

我应该如何取舍?谢谢。

NSAsynchronousFetchRequest有两个主要特点:

  1. 我们不需要单独的上下文(背景,任何),这意味着您可以在主视图上下文中执行它,其他所有内容(比如在需要时创建背景上下文等)将由API。 注意:您仍然需要在完成块中重定向到主线程,因为它可以在任何队列上调用。

  2. 如果初始请求设置为fetchLimit,我们可以直接通过NSAsynchronousFetchResult.progress跟踪获取数据的进度。

1.关于 __Multithreading_Violation_AllThatIsLeftToUsIsHonor__ 异常:

本帖有详细讨论:
CoreData asynchronous fetch causes concurrency debugger error

一致认为这是 CoreData 中的错误。
有一个错误报告:https://openradar.appspot.com/30692722 在撰写本文时 8 年后仍然开放。

2。如何正确使用NSAsynchronousFetchRequest

API 于 2014 年推出,并在此 WWDC 视频 225_sd_whats_new_in_core_data 中进行了讨论。
没有提到 NSAsynchronousFetchRequest 应该在主(视图)上下文或后台上下文中使用。

我查看了在 GitHub 上使用 NSAsynchronousFetchRequest 的几个随机实现,并找到了主要和背景上下文的示例。

然而,在使用后台上下文时,您必须做的一件事是将提取执行包装在perform块中(documentation)。
在您链接的文章和上面摘录的示例中,缺少此内容!
它应该是这样的:

privateManagedObjectContext.perform {
    do {
        try privateManagedObjectContext.execute(asynchronousFetchRequest)
    } catch let error {
        print("error trying to fetch saving objects:", error.localizedDescription)
    }
}

同一篇文章还有另一个潜在的问题,所以持保留态度:

DispatchQueue.main.async {
    // here the objects in result (belongs to private context) are
    // accessed on the main queue – the whole point is to *not* do that!
    // one might get away with it because it is only read access to id
    // but good luck debugging this...
    let dogs: [Dog] = result.lazy
        .flatMap { [=11=].objectID }
        .flatMap { mainManagedObjectContext.object(with: [=11=]) as? Dog }
    // ...

我的理解NSAsynchronousFetchRequest是最好从主上下文中使用,其目的其实是对你隐藏后台上下文业务

所以:mainContext + NSAsynchronousFetchRequest

3。我该如何取舍?

在我看来,创建 NSAsynchronousFetchRequest 的初衷是为了简化我们的异步核心数据获取。为此,您可以充分利用它,尤其是当您需要处理进度和取消时。

但是我可能不会在我的项目中使用它,因为

  • 文档稀疏
  • 维护得不好(永远打开错误)
  • 它没有被很好地采用(例如流行和优秀的 CoreData 包装器 CoreStore 没有使用它)

最后一个想法 - 在开始异步获取之前,请确保您确实需要它。最好先优化查询、数据模型或批处理设置的性能。