You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Swift DiffableDataSource在Firebase .observe()更新值时崩溃如何解决?

问题描述

我正在开发一款文章阅读类应用,文章页内点击作者头像可跳转至作者个人主页。
作者主页内包含Follow按钮,同时展示作者相关统计数据(如发布文章数量、粉丝数等),界面样式参考下图:
作者主页界面参考
页面首次加载时运行正常,但用户点击Follow按钮后应用立即崩溃,报错信息如下:

Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Section identifier count does not match data source count. This is most likely due to a hashing issue with the identifiers.'

复现步骤

  • 用户进入作者个人主页
  • 页面展示Follow按钮以及作者粉丝数等统计数据,假设当前作者粉丝数为10
  • 用户点击Follow按钮
  • 后台逻辑通过.observeSingleEvent()获取作者数据,调用setValue()将当前用户ID添加到followers字符串数组:followers: ["asdm132123", "asdadsa12931", "!123123jsdfjsf"]中
  • 随后通过Firebase的.observe()方法监听数据更新,将按钮状态从Follow改为Following,同时将粉丝数从10更新为11,此时触发崩溃

相关代码

枚举定义

enum AuthorProfileSection {
    case details
    case stats
    case articles
}

数据源创建代码

func configureDataSource() -> UICollectionViewDiffableDataSource<AuthorProfileSection, AnyHashable> {
 
    let dataSource = UICollectionViewDiffableDataSource<AuthorProfileSection, AnyHashable>(collectionView: collectionView) { (collectionView, indexPath, object) -> UICollectionViewCell? in
 
        
        if let object = object as? Author {
            let cell = collectionView.dequeueReusableCell(withReuseIdentifier: AuthorDetailsCell.reuseIdentifier, for: indexPath) as! AuthorDetailsCell
            cell.configure(with: object)
            return cell
        }
        
        
        if let object = object as? AuthorStatsForAuthorProfile {
            let cell = collectionView.dequeueReusableCell(withReuseIdentifier: AuthorStatCell.reuseIdentifier, for: indexPath) as! AuthorStatCell
            cell.configure(with: object)
            return cell
        }
        
        
        if let object = object as? Article {
            let cell = collectionView.dequeueReusableCell(withReuseIdentifier: AuthorArticleCell.reuseIdentifier, for: indexPath) as! AuthorArticleCell

            cell.configure(with: object)
            return cell
        }
        
        return nil
    }
 
    return dataSource
}

快照更新代码

fileprivate func updateSnapshot(animated: Bool = false) {
    
    guard let authorUID = authorUID else { return }

    Firebase.Database.database().fetchSelectedAuthorProfileData( authorUID: authorUID, fromArticleUID: self.articleUID) { authorProfileSection in
        
        var snapshot = self.dataSource.snapshot()
        
        // sections
        snapshot.appendSections([.details, .stats, .articles])
        snapshot.appendItems([authorProfileSection.details], toSection: .details)
        snapshot.appendItems(authorProfileSection.stats ?? [], toSection: .stats)
        snapshot.appendItems(authorProfileSection.articles ?? [], toSection: .articles)

        self.dataSource.apply(snapshot, animatingDifferences: animated)
    }
}

Cell代码

class AuthorStatCell: UICollectionViewCell, SelfConfiguringCell {

    typealias someType = AuthorStatsForAuthorProfile

    static var reuseIdentifier = "AuthorStatCell"

    override init(frame: CGRect) {
        super.init(frame: frame)
        buildUI()
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    func configure(with data: AuthorStatsForAuthorProfile) {
        print(data)
    }

    fileprivate func buildUI() {
        backgroundColor = UIColor(red: 0.20, green: 0.83, blue: 0.60, alpha: 1.00)
        layer.cornerRadius = 15
    }
}

界面效果如下,红色区域为点击关注按钮后会变更的内容:
界面效果示意


崩溃原因

核心原因

快照更新逻辑存在错误:你每次更新快照时,都是基于当前数据源已有的快照(首次加载后已经包含.details、.stats、.articles三个section)直接调用appendSections重复添加相同的section。首次加载时快照为空,添加操作无异常;点击关注后触发数据回调,第二次更新快照时重复添加已存在的section标识符,导致快照内部的section计数和预期不符,触发系统的一致性校验异常,就是你看到的崩溃报错。

额外风险点

你使用AnyHashable作为DiffableDataSource的item类型,如果AuthorStatsForAuthorProfile、Author、Article等数据模型没有正确实现Hashable协议(例如没有将数据的唯一ID作为哈希计算的核心依据,而是用全量属性计算哈希),当数据属性更新(比如粉丝数变化)后,新实例和旧实例的哈希值不匹配,会导致快照无法正确识别为同一条数据的更新,反而触发新增/删除逻辑异常,也会引发同类崩溃。

修复方案

  1. 每次更新快照时创建全新的快照实例,不要复用当前数据源的已有快照,避免重复添加已存在的section和item,将快照更新代码中的var snapshot = self.dataSource.snapshot()修改为var snapshot = NSDiffableDataSourceSnapshot<AuthorProfileSection, AnyHashable>()即可。
  2. 建议不要使用AnyHashable作为item类型,给所有item模型定义统一的协议并显式实现Hashable,保证每个数据项的唯一标识符作为哈希计算的核心,属性变更不会影响同一条数据的哈希匹配。
  3. 如果需要复用已有快照,要先调用deleteAllItems()、deleteSections()等方法清空旧内容后再添加新内容。

内容的提问来源于stack exchange,提问作者christostsang

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.05 04:12:02