在 viewWillDisappear 中将 FetchedResultsController 设置为 nil 后出现错误

Posted

技术标签:

【中文标题】在 viewWillDisappear 中将 FetchedResultsController 设置为 nil 后出现错误【英文标题】:Bug with FetchedResultsController after setting it to nil in viewWillDisappear 【发布时间】:2017-09-11 13:30:41 【问题描述】:

在我的 ios(Swift 3、Xcode 8)Core Data 应用程序中,我有 2 个视图控制器(CategoriesViewController 和 ObjectsViewController)(两者都在同一个导航控制器内)。

每个 ViewController 都有自己的 tableView 和自己的 fetchResultsController 来管理从 Core Data 请求返回的结果(在 CategoriesViewController 中获取名为 Category 的实体,在 ObjectsViewController 中获取名为 Object 的实体)。

在我的 CategoriesViewController 中有这个变量:

var fetchResultsController: NSFetchedResultsController<Category>!

我已将以下代码添加到 CategoriesViewController 以避免在打开另一个视图时出错:

override func viewWillDisappear(_ animated: Bool) 
          super.viewWillDisappear(true)

          self.fetchedResultsController.delegate = nil
          self.fetchedResultsController = nil

     

在 CategoriesViewController 中,我为 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)
               



          

     

为了获取核心数据对象,我编写了一个 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 返回后(我也有所有的方法,对象实体的 fetchedResultsController 并且我还在 viewWillDisappear 中将 fetchedResultsController 设置为 nil)我在 CategoriesViewController 中的 tableView 冻结。如果我从 CategoriesViewController 的 viewWillDisappear 中删除这两行,一切正常,但我需要这些行来避免其他错误。

self.fetchedResultsController.delegate = nil self.fetchedResultsController = nil

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)。

【问题讨论】:

你能展示你的 viewWillAppear 代码吗? 为什么将 fetchedResultsController 设置为 nil?它还防止了哪些其他错误?这不是个好主意。并且手动设置一些东西 nil 闻起来真的很糟糕的架构......你应该认真看看 fetchedResultsController 是如何工作的以及如何初始化它...... @chris-allwein ,添加了 viewWillAppear 的代码。顺便说一句,在 self.coreData_fetchAll_Categories 完成处理程序中,我有一些代码用于处理核心数据实体的索引值,现在正在检查其中的错误,这可能是因为该代码。 @dominik-bucher ,在包含不同 fetchResultsControllers(具有相同上下文)的视图控制器之间切换时,我遇到了奇怪的错误。因此,添加“将 fetchedResultsController 设置为 nil”有助于解决这些错误。 【参考方案1】:

您必须确保 tableview 和 fetchedResultsController 永远不会不同步。以下是您的代码中可能发生的几个地方:

    当您取消 fetchedResultsController 时,您还必须重新加载 tableview,因为现在它应该有零节和零行 coreData_fetchAll_Categories 已经在主线程上运行 - 没有理由使用这样的完成处理程序。此外,使用DispatchQueue.main.async 可能会造成真正的伤害,因为当 tableview 和 fetchedResultsController 不同步时,在调用 reloadData 之前有一段时间 另外(与您的核心数据问题无关)当您调用 super.viewWillDisappearsuper.viewWillAppear 时,您应该传递 animated 参数 - 而不是总是传递 true

希望有帮助

【讨论】:

感谢@jon-rose,您的回答帮助很大,我删除了不必要的dispatchqueue.main.async 电话,稍微更改了电话顺序,也感谢super.viewWillDisappear 和@987654329 的动画评论@。现在效果很好。

以上是关于在 viewWillDisappear 中将 FetchedResultsController 设置为 nil 后出现错误的主要内容,如果未能解决你的问题,请参考以下文章

尽量不要在viewWillDisappear:方法中移除通知

viewWillAppear、viewDidAppear、viewWillDisappear、viewDidDisappear 指南

viewWillDisappear, viewWillUnload viewDidUnload 从未调用

在 viewWillDisappear 上停止 AVAudioPlayer

为啥 setViewControllers 不会触发 viewWillDisappear 和 dealloc?

使用 NSCoder 进行视图之间的恢复 (viewWillAppear / viewWillDisappear)