UITableView随机出现重复Cell问题求助(基于MovementsDatasource分页实现)
问题原因分析
- 并发请求冲突:当前代码没有限制分页请求的并发执行,若同时触发下拉刷新(调用
getFirstPage)和上拉加载更多(调用getNextPage),多个网络请求先后回调时,会同时操作values数组,导致数据拼接逻辑错乱,出现重复数据。 - 旧请求未取消:调用
getFirstPage重置分页状态时,没有取消已发出但未返回的上一页请求,旧请求后续返回后会把过期数据追加到新数据后,引发重复。 - 无Item去重逻辑:
merge方法仅对分组头做去重,没有对分组内的Movement条目做去重,若接口返回重复条目、或同一条目跨页返回,会直接累加进数据源。 - 线程安全问题:网络请求的回调未指定主线程执行,若在后台线程修改
values数组、同时触发UITableView刷新,会导致数据源和UI渲染不同步,出现偶发的Cell复用错乱。 - Cell复用未清空:
MovementCell复用时没有清空旧内容,若赋值逻辑异常会保留上一次的Cell渲染结果,看起来和重复Cell效果一致。
修复方案
1. 新增请求状态控制、请求取消逻辑
修改MovementsDatasource的核心属性和分页方法:
final class MovementsDatasource: NSObject, PaginatedDatasource { // 原有属性保持不变 private var isRequesting = false // 用可变DisposeBag实现刷新时取消旧请求 private var disposeBag = DisposeBag() func getNextPage() { guard self.hasNextPage, !isRequesting else { return } isRequesting = true getMovements(for: self.id, cvl: self.cvl, page: self.page) } func getFirstPage() { // 取消所有旧请求 disposeBag = DisposeBag() guard !isRequesting else { return } isRequesting = true self.page = 0 self.hasNextPage = false self.values = [] getMovements(for: self.id, cvl: self.cvl, page: self.page) } }
2. 回调指定主线程、更新请求状态
修改getMovements方法:
private func getMovements(for productID: String, cvl: String, page: Int) { self.repository .movements(userCVL: cvl, ibanAccountId: productID, page: page) .trackActivity(self.fetching) // 强制回调到主线程执行 .observe(on: MainScheduler.instance) .subscribe(onNext: { [weak self] response in guard let _self = self else { return } _self.isRequesting = false _self.hasNextPage = response.hasNextPage _self.page = _self.hasNextPage ? (_self.page + 1) : _self.page _self.publish(response) }, onError: { [weak self] _ in self?.isRequesting = false }) .disposed(by: self.disposeBag) }
3. 新增Item去重逻辑
修改merge方法,假设Movement有唯一标识属性transactionId:
internal func merge(_ sections: [MovementsSectionModel]) -> [MovementsSectionModel] { sections .map { $0.header } .unique() .map { title -> MovementsSectionModel in let merged = sections.reduce(into: [MovementCellViewModel]()) { accumulator, section in if section.header == title { // 按唯一ID去重后再添加 let uniqueItems = section.items.filter { item in !accumulator.contains { $0.transactionId == item.transactionId } } accumulator += uniqueItems } } return MovementsSectionModel(header: title, items: merged) } }
4. 优化Cell复用逻辑
在MovementCell类中添加复用清空逻辑:
override func prepareForReuse() { super.prepareForReuse() // 清空所有旧视图内容 self.titleLabel.text = nil self.amountLabel.text = nil self.viewModel = nil }
内容的提问来源于stack exchange,提问作者Zac Mazk
相关产品推荐
相关产品推荐

