在 viewWillDisappear 中将 FetchedResultsController 设置为 nil 后出现错误
Bug with FetchedResultsController after setting it to nil in viewWillDisappear
在我的 iOS (Swift 3, Xcode 8) 核心数据应用程序中,我有 2 个视图控制器(类别ViewController 和 ObjectsViewController)(两者都在同一个导航控制器中)。
每个 ViewController 都有它自己的 tableView 和它自己的 fetchResultsController 来管理核心数据请求返回的结果(获取标题为 Category in CategoriesViewController 的实体和标题为 Object 的实体 ObjectsViewController).
在我的类别中ViewController 我有这个变量:
var fetchResultsController: NSFetchedResultsController<Category>!
我已将以下代码添加到类别ViewController 以避免在打开另一个视图时出错:
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(true)
self.fetchedResultsController.delegate = nil
self.fetchedResultsController = nil
}
在类别中ViewController 我为 fetchedResultsController 添加了这些方法:
func controllerWillChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
self.tableView.beginUpdates()
}
func controllerDidChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
self.tableView.endUpdates()
}
func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChange anObject: Any, at indexPath: IndexPath?, for type: NSFetchedResultsChangeType, newIndexPath: IndexPath?) {
switch type {
case .update:
guard let path = indexPath
else { return }
tableView.reloadRows(at: [path], with: .automatic)
case .delete:
guard let path = indexPath
else { return }
tableView.deleteRows(at: [path],
with: .automatic)
case .insert:
guard let path = newIndexPath
else { return }
tableView.insertRows(at: [path],
with: .automatic)
case .move:
guard let _ = indexPath,
let _ = newIndexPath
else { return }
// tableView.moveRow(at: fromPath, to: toPath)
if indexPath != newIndexPath {
tableView.deleteRows(at: [indexPath!], with: .none)
tableView.insertRows(at: [newIndexPath!], with: .none)
}
}
}
为了获取核心数据 objects 我写了一个 coreData_fetchAll_Categories()。我已将它放入 CategoriesViewController 的 viewWillAppear 方法中。之后我重新加载 tableView 的数据。
func coreData_fetchAll_Categories(handleCompleteFetching:@escaping (()->())) {
let context = CoreDataManager.sharedInstance.viewContext
let fetchRequest: NSFetchRequest<Category> = Category.fetchRequest()
var sortDescriptors = [NSSortDescriptor]()
let indexSortDescriptior = NSSortDescriptor(key: "indexOrder", ascending: true)
sortDescriptors.append(indexSortDescriptior)
fetchRequest.sortDescriptors = sortDescriptors
self.fetchedResultsController = NSFetchedResultsController(fetchRequest: fetchRequest, managedObjectContext: context!, sectionNameKeyPath: nil, cacheName: nil)
self.coreDataFetchedResultsController.delegate = self
do { try self.fetchedResultsController.performFetch()
} catch {
print("performFetch() finished with error")
}
}
使用上面的代码,在我从 ObjectsViewController 返回后(我也有 Object 实体的 fetchedResultsController 的所有方法,我还设置了fetchedResultsController 在 viewWillDisappear 中变为 nil)我在 CategoriesViewController 中的 tableView 冻结了。如果我从 CategoriesViewController 的 viewWillDisappear 中删除这两行,一切正常,但我需要这些行来避免其他错误。
self.fetchedResultsController.delegate = 无
self.fetchedResultsController = 无
ViewWillAppear 中的代码如下所示:
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(true)
self.tableView.register(UINib.init(nibName: “CategoriesTableViewCell", bundle: nil), forCellReuseIdentifier: “categoriesTableViewCell")
self.tableView.delegate = self
self.tableView.dataSource = self
self.coreData_fetchAll_Categories {
DispatchQueue.main.async { // Submit to the main Queue async
self.tableView.reloadData()
}
}
}
在 CategoriesViewController 出现后,VC 创建了新版本的 fetchedResultsController(我检查过,它不是 nil)。我还注意到 tableView 没有调用 cellForRowAt indexPath: 。似乎很奇怪。 tableView 的代表在 viewWillAppear 中设置为 self。
我不明白为什么会出现这个错误,因为我没有收到任何错误。
欢迎对此错误提出任何意见。在 Swift 3 (XCode 8) 工作。
您必须确保 tableview 和 fetchedResultsController 永远不会不同步。以下是您的代码中可能发生的几个地方:
- 当您清空 fetchedResultsController 时,您还必须重新加载表视图,因为现在它应该有零部分和零行
coreData_fetchAll_Categories
已经在主线程上 运行 - 没有理由使用这样的完成处理程序。此外,使用 DispatchQueue.main.async
可能会造成真正的危害,因为当 tableview 和 fetchedResultsController 不同步时,在调用 reloadData 之前有一段时间
- 此外(与您的核心数据问题无关)当您调用
super.viewWillDisappear
和 super.viewWillAppear
时,您应该传递 animated
参数 - 而不是总是传递 true
希望对您有所帮助
在我的 iOS (Swift 3, Xcode 8) 核心数据应用程序中,我有 2 个视图控制器(类别ViewController 和 ObjectsViewController)(两者都在同一个导航控制器中)。
每个 ViewController 都有它自己的 tableView 和它自己的 fetchResultsController 来管理核心数据请求返回的结果(获取标题为 Category in CategoriesViewController 的实体和标题为 Object 的实体 ObjectsViewController).
在我的类别中ViewController 我有这个变量:
var fetchResultsController: NSFetchedResultsController<Category>!
我已将以下代码添加到类别ViewController 以避免在打开另一个视图时出错:
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(true)
self.fetchedResultsController.delegate = nil
self.fetchedResultsController = nil
}
在类别中ViewController 我为 fetchedResultsController 添加了这些方法:
func controllerWillChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
self.tableView.beginUpdates()
}
func controllerDidChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
self.tableView.endUpdates()
}
func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChange anObject: Any, at indexPath: IndexPath?, for type: NSFetchedResultsChangeType, newIndexPath: IndexPath?) {
switch type {
case .update:
guard let path = indexPath
else { return }
tableView.reloadRows(at: [path], with: .automatic)
case .delete:
guard let path = indexPath
else { return }
tableView.deleteRows(at: [path],
with: .automatic)
case .insert:
guard let path = newIndexPath
else { return }
tableView.insertRows(at: [path],
with: .automatic)
case .move:
guard let _ = indexPath,
let _ = newIndexPath
else { return }
// tableView.moveRow(at: fromPath, to: toPath)
if indexPath != newIndexPath {
tableView.deleteRows(at: [indexPath!], with: .none)
tableView.insertRows(at: [newIndexPath!], with: .none)
}
}
}
为了获取核心数据 objects 我写了一个 coreData_fetchAll_Categories()。我已将它放入 CategoriesViewController 的 viewWillAppear 方法中。之后我重新加载 tableView 的数据。
func coreData_fetchAll_Categories(handleCompleteFetching:@escaping (()->())) {
let context = CoreDataManager.sharedInstance.viewContext
let fetchRequest: NSFetchRequest<Category> = Category.fetchRequest()
var sortDescriptors = [NSSortDescriptor]()
let indexSortDescriptior = NSSortDescriptor(key: "indexOrder", ascending: true)
sortDescriptors.append(indexSortDescriptior)
fetchRequest.sortDescriptors = sortDescriptors
self.fetchedResultsController = NSFetchedResultsController(fetchRequest: fetchRequest, managedObjectContext: context!, sectionNameKeyPath: nil, cacheName: nil)
self.coreDataFetchedResultsController.delegate = self
do { try self.fetchedResultsController.performFetch()
} catch {
print("performFetch() finished with error")
}
}
使用上面的代码,在我从 ObjectsViewController 返回后(我也有 Object 实体的 fetchedResultsController 的所有方法,我还设置了fetchedResultsController 在 viewWillDisappear 中变为 nil)我在 CategoriesViewController 中的 tableView 冻结了。如果我从 CategoriesViewController 的 viewWillDisappear 中删除这两行,一切正常,但我需要这些行来避免其他错误。
self.fetchedResultsController.delegate = 无 self.fetchedResultsController = 无
ViewWillAppear 中的代码如下所示:
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(true)
self.tableView.register(UINib.init(nibName: “CategoriesTableViewCell", bundle: nil), forCellReuseIdentifier: “categoriesTableViewCell")
self.tableView.delegate = self
self.tableView.dataSource = self
self.coreData_fetchAll_Categories {
DispatchQueue.main.async { // Submit to the main Queue async
self.tableView.reloadData()
}
}
}
在 CategoriesViewController 出现后,VC 创建了新版本的 fetchedResultsController(我检查过,它不是 nil)。我还注意到 tableView 没有调用 cellForRowAt indexPath: 。似乎很奇怪。 tableView 的代表在 viewWillAppear 中设置为 self。
我不明白为什么会出现这个错误,因为我没有收到任何错误。
欢迎对此错误提出任何意见。在 Swift 3 (XCode 8) 工作。
您必须确保 tableview 和 fetchedResultsController 永远不会不同步。以下是您的代码中可能发生的几个地方:
- 当您清空 fetchedResultsController 时,您还必须重新加载表视图,因为现在它应该有零部分和零行
coreData_fetchAll_Categories
已经在主线程上 运行 - 没有理由使用这样的完成处理程序。此外,使用DispatchQueue.main.async
可能会造成真正的危害,因为当 tableview 和 fetchedResultsController 不同步时,在调用 reloadData 之前有一段时间- 此外(与您的核心数据问题无关)当您调用
super.viewWillDisappear
和super.viewWillAppear
时,您应该传递animated
参数 - 而不是总是传递true
希望对您有所帮助