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

如何重构双模型DiffableDataSource以支持UICollectionView原生更新?

重构方案:让DiffableDataSource支持原生更新

核心问题拆解

你当前的困境本质是两个问题:

  1. 用AnyHashable作为Item标识符,DiffableDataSource无法精准区分User和Chat的身份,也无法感知Chat关联数据的变化;
  2. ChatCell直接依赖外部tagsManager、statusManager,导致UI数据分散在多个地方,DiffableDataSource无法通过快照对比识别需要更新的单元格。

下面是具体的重构步骤,每一步都配代码示例:


1. 替换AnyHashable为明确的Item枚举

先定义一个枚举作为DiffableDataSource的Item类型,明确区分User和Chat的身份,同时保证每个Item的唯一性:

enum CollectionItem: Hashable {
    case user(User)
    case chat(ChatViewModel)
    
    // 基于唯一ID做哈希和相等判断,确保Diffable能识别同一个Item
    func hash(into hasher: inout Hasher) {
        switch self {
        case .user(let user):
            hasher.combine(user.id)
        case .chat(let vm):
            hasher.combine(vm.chat.id)
        }
    }
    
    static func == (lhs: Self, rhs: Self) -> Bool {
        switch (lhs, rhs) {
        case (.user(let lhsUser), .user(let rhsUser)):
            return lhsUser.id == rhsUser.id
        case (.chat(let lhsVM), .chat(let rhsVM)):
            return lhsVM.chat.id == rhsVM.chat.id
        default:
            return false
        }
    }
}

2. 创建ChatViewModel整合所有UI依赖数据

把ChatModel、tagsManager的标签、statusManager的状态全部整合到一个ViewModel里,让Cell只依赖这个ViewModel,同时实现Hashable(基于所有影响UI的属性):

struct ChatViewModel: Hashable {
    let chat: Chat
    let tags: [Tag]
    let status: ChatStatus
    
    // 哈希和相等判断要包含所有会影响UI展示的属性
    func hash(into hasher: inout Hasher) {
        hasher.combine(chat.id)
        hasher.combine(tags) // 前提是Tag实现了Hashable
        hasher.combine(status) // 前提是ChatStatus实现了Hashable
    }
    
    static func == (lhs: Self, rhs: Self) -> Bool {
        return lhs.chat.id == rhs.chat.id 
            && lhs.tags == rhs.tags 
            && lhs.status == rhs.status
    }
}

3. 重构DiffableDataSource配置

更新数据源类型,同时修改Cell配置逻辑,让Cell只接收ViewModel:

// 更新数据源定义
private var dataSource: UICollectionViewDiffableDataSource<Sections, CollectionItem>?

// 初始化数据源时的Cell Provider
dataSource = UICollectionViewDiffableDataSource<Sections, CollectionItem>(collectionView: collectionView) { [weak self] collectionView, indexPath, item in
    guard let self = self else { return nil }
    switch item {
    case .user(let user):
        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: UserCell.reuseID, for: indexPath) as! UserCell
        cell.configure(with: user)
        return cell
    case .chat(let vm):
        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: ChatCell.reuseID, for: indexPath) as! ChatCell
        cell.configure(with: vm) // 现在Cell不需要依赖外部Manager了
        return cell
    }
}

4. 重构快照生成与更新逻辑

生成快照时,把Chat转换成包含所有依赖数据的ViewModel;当外部数据变化时,更新对应的ViewModel并调用原生的reconfigureItems:

// 生成完整快照
private func createFullSnapshot() -> NSDiffableDataSourceSnapshot<Sections, CollectionItem> {
    var snapshot = NSDiffableDataSourceSnapshot<Sections, CollectionItem>()
    snapshot.appendSections([.users, .chats])
    
    // 添加User项
    let userItems = self.users.map { CollectionItem.user($0) }
    snapshot.appendItems(userItems, toSection: .users)
    
    // 添加Chat项,整合外部数据
    let chatItems = self.chats.map { chat in
        let tags = self.tagsManager.tags(for: chat.id)
        let status = self.statusManager.status(for: chat.id)
        return CollectionItem.chat(ChatViewModel(chat: chat, tags: tags, status: status))
    }
    snapshot.appendItems(chatItems, toSection: .chats)
    
    return snapshot
}

// 局部更新某个Chat的标签/状态
func updateChatExternalData(for chatId: String) {
    guard let targetChat = chats.first(where: { $0.id == chatId }) else { return }
    
    // 生成更新后的ViewModel
    let updatedTags = tagsManager.tags(for: chatId)
    let updatedStatus = statusManager.status(for: chatId)
    let updatedVM = ChatViewModel(chat: targetChat, tags: updatedTags, status: updatedStatus)
    let updatedItem = CollectionItem.chat(updatedVM)
    
    // 用原生方法更新
    var currentSnapshot = dataSource?.snapshot() ?? createFullSnapshot()
    if currentSnapshot.contains(updatedItem) {
        currentSnapshot.reconfigureItems([updatedItem])
        dataSource?.apply(currentSnapshot, animatingDifferences: true)
    }
}

// 全量更新方法保留(用于数据源整体变化时)
func reloadDiffableDataSource() {
    let snapshot = createFullSnapshot()
    dataSource?.apply(snapshot, animatingDifferences: true)
}

额外注意事项

  • 确保User、Tag、ChatStatus都正确实现了Hashable,否则ViewModel的哈希判断会失效;
  • 给tagsManager、statusManager添加变化通知(比如用Combine、Delegate或者闭包回调),在ViewController里监听这些通知,触发对应的updateChatExternalData方法;
  • 彻底移除ChatCell对外部Manager的依赖,让Cell只负责接收ViewModel并展示,实现数据驱动UI。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.19 16:40:10