У меня есть настройка проекта с CoreData, которая использует NSFetchedResultsController
для отображения списка Students
. Я использую NSPersistentCloudKitContainer
для синхронизации базы данных с CloudKit
.
У меня простой экран. При каждом запуске приложения первый экран показывает список всех Students
, используя NSFetchedResultsController
. У меня есть тысячи Student
объектов в моей базе данных, и если я переустановил приложение, NSPersistentCloudKitContainer
автоматически начнет извлекать Students
из CloudKit
, и мой UItableView
начнет обновляться с помощью NSFetchedResultsController
всего выглядит волшебно, пока приложение внезапно не завершится со следующей ошибкой:
CoreData: fault: серьезная ошибка приложения. Исключение было получено от делегата NSFetchedResultsController во время вызова -controllerDidChangeContent :. Попытка вставить строку 6 в раздел 88, но в разделе 88 после обновления с userInfo (null) есть только 3 строки
Я действительно не мог понять, почему происходит сбой приложения. Именно во время просмотра таблицы пытались обновить записи, но возникли некоторые несоответствия. Я использую стандартный шаблонный код для NSFetchedResultsControllerDelegate
. Ниже моя реализация NSFetchedResultsControllerDelegate
extension StudentTableViewController : NSFetchedResultsControllerDelegate{
public func controllerWillChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
studentTableView.beginUpdates()
}
public func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChange sectionInfo: NSFetchedResultsSectionInfo, atSectionIndex sectionIndex: Int, for type: NSFetchedResultsChangeType) {
switch type {
case .insert: studentTableView.insertSections([sectionIndex], with: .automatic)
case .delete: studentTableView.deleteSections([sectionIndex], with: .automatic)
default: break
}
}
public func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChange anObject: Any, at indexPath: IndexPath?, for type: NSFetchedResultsChangeType, newIndexPath: IndexPath?) {
switch type {
case .insert:
if let newIndexPath = newIndexPath{
studentTableView.insertRows(at: [newIndexPath], with: .automatic)
}
case .delete:
if let indexPath = indexPath{
studentTableView.deleteRows(at: [indexPath], with: .automatic)
}
case .update:
if let indexPath = indexPath{
studentTableView.reloadRows(at: [indexPath], with: .automatic)
}
case .move:
if let indexPath = indexPath{
studentTableView.deleteRows(at: [indexPath], with: .automatic)
}
if let newIndexPath = newIndexPath{
studentTableView.insertRows(at: [newIndexPath], with: .automatic)
}
@unknown default:
break
}
}
public func controllerDidChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
studentTableView.endUpdates()
}
}
Альтернативный подход
Поскольку у меня тысячи синхронизируемых объектов, он должен поступать партиями, я также получаю NSPersistentStoreRemoteChange
уведомление срабатывает почти 30 раз в течение синхронизации c. TableView обновляется, пока происходит syn c. Я подумал, почему бы не дождаться завершения syn c, а затем просто перезагрузить TableView после завершения syn c, и это не приведет к ненужному обновлению представления таблицы. sh для каждого изменения.
, чтобы отключить автоматическую синхронизацию c, я установил
viewContext.automaticallyMergesChangesFromParent = false
viewContext.setQueryGenerationFrom(.current)
, что делает управляемый текст изолированным от любого изменения фона в постоянном хранилище. Я установил задержку Timer
на 5 секунд, и при каждом срабатывании уведомления NSPersistentStoreRemoteChange
я сбрасывал Timer
. Если в течение 5 секунд не происходит никаких изменений, я пытаюсь синхронизировать c изменения вручную, обновляя queryGeneration до последнего текущего состояния постоянного хранилища.
// Pin the viewContext to the current generation token
do {
try container.viewContext.setQueryGenerationFrom(.current)
} catch {
fatalError("###\(#function): Failed to pin viewContext to the current generation:\(error)")
}
Также Apple упомянула в своих документах для разработчиков [Ссылка] :
Refre sh Objects
Refre sh любые управляемые объекты, зарегистрированные в контексте после изменения генерации запроса контекста или открепления контекста. Управляемые объекты автоматически не обновляют sh, так как это поведение может быть нежелательным и его трудно восстановить. Вызовите refreshAllObjects () для контекста, чтобы обновить sh его существующих управляемых объектов.
, вызвав refreshAllObjects () для viewContext, замораживая пользовательский интерфейс на несколько секунд, но он получает правильные данные. Как я могу избежать зависания пользовательского интерфейса во время вызова refreshAllObjects ()?
Есть ли какая-либо ошибка в моем NSFetchedResultsControllerDelegate
или есть какой-либо способ refreshAllObjects без зависания пользовательского интерфейса?