FRC之controller(_:didChangeContentWith:)詳解

在NSFetchedResultsControllerDelegate中,有一個代理方法:

optional func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChangeContentWith snapshot: NSDiffableDataSourceSnapshot)

這個代理方法是為UITableViewDiffableDataSourceh和UICollectionViewDiffableDataSource量身定制的,目的是便于更加簡單的處理數(shù)據(jù)的更新。
首先:我們創(chuàng)建一個FRC:

    private func setupFetchedResultsController() {
        fetchedResultsController = DatabaseManager.createTodoItemsFetchedResultsController()
        fetchedResultsController?.delegate = self
        try? fetchedResultsController?.performFetch()
    }

創(chuàng)建UITableViewDiffableDataSource:

    private func setupDataSource() {
        dataSource = TodoListDiffableDataSource<String, NSManagedObjectID>(tableView: tableView, cellProvider: { [weak self] tableView, indexPath, objectID in
            guard let cell = tableView.dequeueReusableCell(withIdentifier: TodoListCell.reuseIdentifier, for: indexPath) as? TodoListCell else {
                fatalError("Cannot dequeue TodoListCell")
            }

            let context = self?.fetchedResultsController?.managedObjectContext
            let todoItem = context?.object(with: objectID) as? TodoItem
            cell.configure(item: todoItem, shouldShowCheckmark: true)
            return cell
        })
    }

實現(xiàn)NSFetchedResultsControllerDelegate

    func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChangeContentWith snapshot: NSDiffableDataSourceSnapshotReference) {
        guard let dataSource = dataSource else { return }
        dataSource.apply(snapshot as NSDiffableDataSourceSnapshot<String, NSManagedObjectID>, animatingDifferences: false)
    }

新增一個實體:

let item = DatabaseManager.shared.createTodoItem()
item.title = "試試"
item.isCompleted = false
try? DatabaseManager.shared.saveContext()

當調(diào)用saveContext()時,會觸發(fā)FRC的controller(:didChangeContentWith:),如果此時cell是可見的,能正常展示,但是如果后續(xù)再次刷新tableView或者滑動tableView使得cell刷新,那么數(shù)據(jù)將會為空,這一切都是由于controller(:didChangeContentWith:)方法中,snapshot中的objectID是臨時ID導致,下面我直接給出這個方法的詳解:

/**
     1. 這個代理方法一旦實現(xiàn),其余傳統(tǒng)的代理方法將全部失效。
     2. `snapshot`,蘋果定義為 `NSDiffableDataSourceSnapshot<String, NSManagedObjectID>` 類型。
     3. 當新建一個 NSManagedObject 子類實體(以下簡稱 MO)時,Core Data 會為該實體分配一個臨時 objectID,
       只有在真正提交到數(shù)據(jù)庫之后,系統(tǒng)才會將臨時 objectID 轉(zhuǎn)化為永久的 objectID,同時可能將臨時 objectID 綁定的 MO 銷毀。
       本方法的 `snapshot` 中新增的實體的 objectID 就是臨時的,換句話說,這個代理方法是在saveContext()時,臨時ID轉(zhuǎn)化為永久ID之前觸發(fā)。
       所以這會導致這樣一種現(xiàn)象:
       當這個代理方法觸發(fā)時,dataSource 調(diào)用 `apply()` 進而觸發(fā) tableView 刷新,
       因為此時臨時ID還沒轉(zhuǎn)化為永久ID,臨時 objectID 綁定的 MO 還未銷毀,所以此時在 `cellProvider` 中通過臨時 objectID 還是可以正常取到 MO 的,tableView 能正常展示,
       而當某一刻再次刷新 tableView 或者手動滑動 tableView 使得 cell 刷新,會發(fā)現(xiàn)新增的 MO 數(shù)據(jù)全部為空,這是因為刷新 tableView 之前,臨時ID已經(jīng)成功轉(zhuǎn)化為了永久ID
       也就是臨時 objectID 綁定的 MO 已經(jīng)被銷毀了,再通過臨時 objectID 就取不到了。
       要解決這個問題:
       方法一(推薦):可以在 `save` 之前,先獲取永久 ID,如:`context.obtainPermanentIDs(for: [MO])`.
       方法二: 通過 child context 創(chuàng)建 MO,這樣 `save` 的時候,就只會提交到 parent context,
              經(jīng)測試,即便最后由root context提交到數(shù)據(jù)庫,只要是child context創(chuàng)建的MO,MO都不會被銷毀.
       方法三: 在每次 `save` 之后,調(diào)用一遍 `performFetch()`,`performFetch()` 會從數(shù)據(jù)庫中抓取數(shù)據(jù)存在 context 中,
              會自動觸發(fā) `controller(_:didChangeContentWith:)`,這個時候的 objectID 肯定是永久的。但這種方式比較蠻力,不推薦。
              警告:不要在 `controller(_:didChangeContentWith:)` 中調(diào)用 `performFetch()`,會發(fā)生死循環(huán)。
     4. 有了這個代理方法,我們無需自己初始化快照,直接用本方法提供的 `snapshot` 即可。
     5. 如果想自己創(chuàng)建快照,比如 `NSDiffableDataSourceSnapshot<S, T>`,通過本方法更新快照,
       需要將 `NSDiffableDataSourceSnapshot<String, NSManagedObjectID>` 轉(zhuǎn)化為   `NSDiffableDataSourceSnapshot<S, T>`
    */
    func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChangeContentWith snapshot: NSDiffableDataSourceSnapshotReference)
最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容