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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.29 07:27:03