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

