在处理 CoreData、NSFetchedResultsController 和 Diffable 数据源时,我总是注意到我需要应用DispatchQueue.main.async
.
例如,
在应用 DispatchQueue.main.async 之前
extension ViewController: NSFetchedResultsControllerDelegate {
func controller(_ fetchedResultsController: NSFetchedResultsController<NSFetchRequestResult>, didChangeContentWith snapshotReference: NSDiffableDataSourceSnapshotReference) {
guard let dataSource = self.dataSource else {
return
}
var snapshot = snapshotReference as NSDiffableDataSourceSnapshot<String, NSManagedObjectID>
dataSource.apply(snapshot, animatingDifferences: true) { [weak self] in
guard let self = self else { return }
}
}
}
但是,在我们运行performFetch
后viewDidLoad
,我会收到以下错误dataSource.apply
'检测到死锁:不允许在未完成异步更新的主队列上调用此方法,并且会死锁。请始终在主队列上或始终在主队列外提交更新
我可以使用以下方法“解决”问题
应用 DispatchQueue.main.async 后
extension ViewController: NSFetchedResultsControllerDelegate {
func controller(_ fetchedResultsController: NSFetchedResultsController<NSFetchRequestResult>, didChangeContentWith snapshotReference: NSDiffableDataSourceSnapshotReference) {
DispatchQueue.main.async { [weak self] in
guard let self = self else { return }
guard let dataSource = self.dataSource else {
return
}
var snapshot = snapshotReference as NSDiffableDataSourceSnapshot<String, NSManagedObjectID>
dataSource.apply(snapshot, animatingDifferences: true) { [weak self] in
guard let self = self else { return }
}
}
}
}
之后一切正常。
但是,我们对为什么DispatchQueue.main.async
需要它感到困惑,因为
performFetch
在主线程中运行。- 回调
didChangeContentWith
在主线程中运行。 NSFetchedResultsController
正在使用主 CoreData 上下文,而不是背景上下文。
DispatchQueue.main.async
因此,如果不使用,我们无法理解为什么会出现运行时错误。
你知道为什么在使用 CoreData、NSFetchedResultsController 和 Diffable 数据源时需要 DispatchQueue.main.async 吗?
以下是我们的详细代码片段。
CoreDataStack.swift
import CoreData
class CoreDataStack {
public static let INSTANCE = CoreDataStack()
private init() {
}
lazy var persistentContainer: NSPersistentContainer = {
let container = NSPersistentContainer(name: "xxx")
container.loadPersistentStores(completionHandler: { (storeDescription, error) in
if let error = error as NSError? {
// This is a serious fatal error. We will just simply terminate the app, rather than using error_log.
fatalError("Unresolved error \(error), \(error.userInfo)")
}
})
// So that when backgroundContext write to persistent store, container.viewContext will retrieve update from
// persistent store.
container.viewContext.automaticallyMergesChangesFromParent = true
// TODO: Not sure these are required...
//
//container.viewContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy
//container.viewContext.undoManager = nil
//container.viewContext.shouldDeleteInaccessibleFaults = true
return container
}()
lazy var backgroundContext: NSManagedObjectContext = {
let backgroundContext = persistentContainer.newBackgroundContext()
// TODO: Not sure these are required...
//
backgroundContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy
//backgroundContext.undoManager = nil
return backgroundContext
}()
// https://www.avanderlee.com/swift/nsbatchdeleterequest-core-data/
func mergeChanges(_ changes: [AnyHashable : Any]) {
// TODO:
//
// (1) Should this method called from persistentContainer.viewContext, or backgroundContext?
// (2) Should we include backgroundContext in the into: array?
NSManagedObjectContext.mergeChanges(
fromRemoteContextSave: changes,
into: [persistentContainer.viewContext, backgroundContext]
)
}
}
NoteViewController.swift
class NoteViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
...
initDataSource()
initNSTabInfoProvider()
}
private func initNSTabInfoProvider() {
self.nsTabInfoProvider = NSTabInfoProvider(self)
// Trigger performFetch
_ = self.nsTabInfoProvider.fetchedResultsController
}
private func initDataSource() {
let dataSource = DataSource(
collectionView: tabCollectionView,
cellProvider: { [weak self] (collectionView, indexPath, objectID) -> UICollectionViewCell? in
guard let self = self else { return nil }
...
}
)
self.dataSource = dataSource
}
NSTabInfoProvider.swift
import Foundation
import CoreData
// We are using https://github.com/yccheok/earthquakes-WWDC20 as gold reference.
class NSTabInfoProvider {
weak var fetchedResultsControllerDelegate: NSFetchedResultsControllerDelegate?
lazy var fetchedResultsController: NSFetchedResultsController<NSTabInfo> = {
let fetchRequest = NSTabInfo.fetchSortedRequest()
// Create a fetched results controller and set its fetch request, context, and delegate.
let controller = NSFetchedResultsController(
fetchRequest: fetchRequest,
managedObjectContext: CoreDataStack.INSTANCE.persistentContainer.viewContext,
sectionNameKeyPath: nil,
cacheName: nil
)
controller.delegate = fetchedResultsControllerDelegate
// Perform the fetch.
do {
try controller.performFetch()
} catch {
error_log(error)
}
return controller
}()
var nsTabInfos: [NSTabInfo]? {
return fetchedResultsController.fetchedObjects
}
init(_ fetchedResultsControllerDelegate: NSFetchedResultsControllerDelegate) {
self.fetchedResultsControllerDelegate = fetchedResultsControllerDelegate
}
func getNSTabInfo(_ indexPath: IndexPath) -> NSTabInfo? {
guard let sections = self.fetchedResultsController.sections else { return nil }
return sections[indexPath.section].objects?[indexPath.item] as? NSTabInfo
}
}