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

如何在RxTableView中清空数据?基于RxCocoa的MVVM无限滚动场景

解决RxCocoa+MVVM下TableView数据清空与无限滚动的问题

核心思路是给scan操作符添加重置触发逻辑,让它能在需要时清空累加的数据源,再重新开始累加新数据。以下是两种可行的实现方案:

方案一:用枚举区分数据更新类型(推荐)

通过在ViewModel中定义事件枚举,明确区分「重置数据」和「追加数据」两种操作,让scan能清晰处理不同场景:

ViewModel 代码调整

import RxSwift
import RxCocoa

// 定义列表更新事件类型
enum HotDealUpdateEvent {
    case reset([HotDeal])  // 清空并替换为新数据
    case append([HotDeal]) // 在现有数据后追加
}

class HotDealViewModel {
    let disposeBag = DisposeBag()
    // 把原有的hotDealList改成事件类型的BehaviorSubject
    let hotDealUpdate = BehaviorSubject<HotDealUpdateEvent>(value: .reset([]))
    
    func requestData(isReset: Bool) {
        // 替换成你的实际API调用逻辑
        APIService.requestHotDeals()
            .subscribe(onNext: { [weak self] fetchedDeals in
                let event = isReset ? HotDealUpdateEvent.reset(fetchedDeals) : HotDealUpdateEvent.append(fetchedDeals)
                self?.hotDealUpdate.onNext(event)
            })
            .disposed(by: disposeBag)
    }
}

ViewController 绑定逻辑

import RxSwift
import RxCocoa

class HotDealViewController: UIViewController {
    let disposeBag = DisposeBag()
    var viewModel = HotDealViewModel()
    @IBOutlet weak var tableView: UITableView!
    let refreshControl = UIRefreshControl()

    override func viewDidLoad() {
        super.viewDidLoad()
        setupTableView()
        bindData()
        // 首次加载:重置数据
        viewModel.requestData(isReset: true)
    }
    
    private func setupTableView() {
        tableView.register(UINib(nibName: "HotDealCell", bundle: nil), forCellReuseIdentifier: "HotDealCell")
        tableView.addSubview(refreshControl)
    }
    
    private func bindData() {
        // 用scan处理事件,实现清空/累加逻辑
        viewModel.hotDealUpdate
            .scan([]) { currentData, event in
                switch event {
                case .reset(let newData):
                    return newData // 清空现有数据,替换为新数据
                case .append(let moreData):
                    return currentData + moreData // 追加新数据
                }
            }
            .bind(to: tableView.rx.items(cellIdentifier: "HotDealCell", cellType: HotDealCell.self)) { index, deal, cell in
                cell.configure(with: deal)
            }
            .disposed(by: disposeBag)
        
        // 下拉刷新:触发重置
        refreshControl.rx.controlEvent(.valueChanged)
            .subscribe(onNext: { [weak self] in
                self?.viewModel.requestData(isReset: true)
                self?.refreshControl.endRefreshing()
            })
            .disposed(by: disposeBag)
        
        // 无限滚动(触底加载更多):触发追加
        tableView.rx.reachedBottom // 假设你已实现触底检测的扩展方法
            .subscribe(onNext: { [weak self] in
                self?.viewModel.requestData(isReset: false)
            })
            .disposed(by: disposeBag)
    }
}

方案二:合并重置信号与数据信号

如果不想修改ViewModel的输出类型,可以在ViewController中单独定义重置信号,与原有的hotDealList合并后再处理:

ViewController 代码调整

private func bindData() {
    // 定义重置信号,用于触发清空操作
    let resetTrigger = PublishSubject<Void>()
    
    // 合并重置信号和数据信号:重置时发送nil,正常加载时发送数据数组
    Observable.merge(
        resetTrigger.map { _ in nil as [HotDeal]? },
        viewModel.hotDealList.map { $0 as [HotDeal]? }
    )
    .scan([]) { currentData, newData in
        guard let newData = newData else {
            return [] // 收到重置信号,清空数据
        }
        return currentData + newData // 收到数据,累加
    }
    .bind(to: tableView.rx.items(cellIdentifier: "HotDealCell", cellType: HotDealCell.self)) { index, deal, cell in
        cell.configure(with: deal)
    }
    .disposed(by: disposeBag)
    
    // 首次加载:先发送重置信号
    resetTrigger.onNext(())
    viewModel.requestData()
    
    // 下拉刷新:触发重置
    refreshControl.rx.controlEvent(.valueChanged)
        .subscribe(onNext: { [weak self] in
            resetTrigger.onNext(())
            self?.viewModel.requestData()
            self?.refreshControl.endRefreshing()
        })
        .disposed(by: disposeBag)
    
    // 触底加载更多
    tableView.rx.reachedBottom
        .subscribe(onNext: { [weak self] in
            self?.viewModel.requestData()
        })
        .disposed(by: disposeBag)
}

关键说明

  • 原问题的核心是scan操作符一直在累加数据,没有重置的触发条件,所以必须引入外部信号或明确的事件类型来通知scan清空累加器。
  • 方案一更符合MVVM的职责分离原则,把数据更新的语义封装在ViewModel中,ViewController只负责UI触发和绑定。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.22 15:51:16