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

RxDataSource嵌套TableViewCell内CollectionView代理冲突报错如何解决

问题根因
  1. 自定义ItemComponentTableViewCell复用时,每次调用configure方法都会重新执行items绑定逻辑,RxCocoa每次绑定都会尝试替换UICollectionView的dataSource,重复绑定触发了代理已存在的断言。
  2. 手动将collectionView的dataSource/delegate置为nil后,RxCocoa内部持有的代理Proxy对象和当前collectionView的实际代理不匹配,触发Proxy变更报错。
  3. registerCell方法每次Cell出列都被调用,属于重复无意义操作,也会增加不必要的性能开销。
解决方案
  1. 绑定逻辑、Cell注册逻辑仅执行一次,统一放到awakeFromNib方法中实现,不要在每次配置Cell时重复执行。
  2. 可复用Cell内的DisposeBag需要在Cell复用时重置,销毁之前的订阅关系,避免旧订阅仍然生效、重复绑定问题。
  3. 不要手动修改UICollectionView的dataSource/delegate属性,由RxCocoa内部管理代理Proxy。
修改后的核心代码

自定义TableViewCell代码

class ItemComponentTableViewCell: UITableViewCell {
    @IBOutlet weak var itemComponentCollectionView: UICollectionView!
    
    private let components = BehaviorSubject<[String]>(value: [])
    // 改为可变属性,复用的时候重新初始化销毁旧订阅
    private var disposeBag = DisposeBag()
    
    override func awakeFromNib() {
        super.awakeFromNib()
        // 注册仅执行一次
        itemComponentCollectionView.register(
            UINib(nibName: "ComponentCollectionViewCell", bundle: nil),
            forCellWithReuseIdentifier: "ComponentCollectionViewCell"
        )
        // 绑定仅执行一次
        setupCollectionBinding()
    }
    
    override func prepareForReuse() {
        super.prepareForReuse()
        // 重置disposeBag,销毁之前的所有订阅
        disposeBag = DisposeBag()
        // 重新初始化绑定逻辑
        setupCollectionBinding()
    }
    
    private func setupCollectionBinding() {
        components
            .bind(to: itemComponentCollectionView.rx.items(
                cellIdentifier: "ComponentCollectionViewCell",
                cellType: ComponentCollectionViewCell.self
            )) { _, element, cell in
                cell.configure(element)
            }
            .disposed(by: disposeBag)
    }
    
    func configure(_ viewModel: ItemDetailViewModelPlus) {
        // 仅发送新数据,不做绑定操作
        components.onNext(viewModel.components)
    }
}

DataSource代码调整

删除DataSource中调用cell.registerCell()的冗余代码,调整后逻辑如下:

struct ItemDetailDataSource {
    typealias DataSource = RxTableViewSectionedReloadDataSource
    
    static func dataSource() -> DataSource<ItemDetailTableViewSection> {
        return .init { (dataSource, tableView, indexPath, item) -> UITableViewCell in
            
            switch dataSource[indexPath] {
            case .itemInfoTopItem(let info):
            case .itemHintItem(let hint):
            case .itemManaColdownItem(let manacd):
            case .itemNotesItem(let notes):
            case .itemAttribItem(let attrib):
            case .itemLoreItem(let lore):
            case .itemComponentsItem(let components):
                guard let cell = tableView
                        .dequeueReusableCell(withIdentifier: ConstantsForCell.itemComponentTableViewCell, for: indexPath)
                        as? ItemComponentTableViewCell else {
                    return UITableViewCell()
                }
                // 删除冗余的cell.registerCell()调用
                cell.configure(components)
                return cell
            }
        }
    }
}
注意事项

所有需要复用的UI组件(TableViewCell、CollectionViewCell、HeaderFooter等)中的Rx订阅都需要配合复用生命周期方法prepareForReuse重置DisposeBag,避免旧订阅泄漏、重复绑定等问题。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.28 10:39:03