使用RxSwift实现下拉刷新与加载更多时无限触发请求的问题
解决RxSwift下拉刷新/加载更多无限触发问题
你遇到的核心问题是缺少“无更多数据”的状态控制:当下一页请求返回空数据时,你的代码没有标记“已加载完全部数据”,再加上滚动到底部的事件会持续触发,就导致了loadNextPageTrigger被无限调用的情况。下面是具体的分析和修复方案:
问题根源拆解
- 没有标记“是否还有更多数据”的状态变量,只要滚动到底部就会尝试发起请求
- 每次触发
loadNextPageTrigger时,不管当前是否还有数据,都会递增pageIndex并发起请求 - 空数据返回后没有终止后续加载的逻辑,导致循环触发
修复方案
我们需要添加状态控制,同时优化请求触发的条件,具体步骤如下:
1. 新增状态变量
在MomentViewModel中添加hasMoreData变量,用来标记是否还有更多数据可以加载:
private let hasMoreData = Variable<Bool>(true)
2. 优化请求触发条件
修改refreshRequest和nextPageRequest的触发逻辑,加入hasMoreData的校验:
- 刷新时重置
hasMoreData为true(保证下拉刷新后可以重新加载分页数据) - 下一页请求只有在未加载中且还有更多数据时才允许发起
3. 根据响应更新状态
请求返回数据后,根据返回结果判断是否还有更多数据(优先使用接口返回的分页标记,比如isLastPage;如果没有则通过返回数据的数量判断)
完整修改后的ViewModel代码
public final class MomentViewModel { // Property let refreshTrigger = PublishSubject<Void>() let loadNextPageTrigger = PublishSubject<Void>() let loading = Variable<Bool>(false) let posts = Variable<[Post]>([]) var pageIndex: Int = 0 let error = PublishSubject<Swift.Error>() // 新增:标记是否还有更多数据可加载 private let hasMoreData = Variable<Bool>(true) private let disposeBag = DisposeBag() public init() { let refreshRequest = Observable.combineLatest(loading.asObservable(), hasMoreData.asObservable()) .sample(refreshTrigger) .flatMap { [unowned self] (loading, _) -> Observable<Int> in if loading { return Observable.empty() } else { return Observable<Int>.create { observer in self.pageIndex = 0 // 刷新时重置"有更多数据"状态 self.hasMoreData.value = true print("reset page index to 0") observer.onNext(0) observer.onCompleted() return Disposables.create() } } } .debug("refreshRequest", trimOutput: false) let nextPageRequest = Observable.combineLatest(loading.asObservable(), hasMoreData.asObservable()) .sample(loadNextPageTrigger) .flatMap { [unowned self] (loading, hasMore) -> Observable<Int> in // 只有未加载且还有更多数据时,才发起下一页请求 if loading || !hasMore { return Observable.empty() } else { return Observable<Int>.create { [unowned self] observer in self.pageIndex += 1 print(self.pageIndex) observer.onNext(self.pageIndex) observer.onCompleted() return Disposables.create() } } } .debug("nextPageRequest", trimOutput: false) let request = Observable.merge(refreshRequest, nextPageRequest) .debug("Request", trimOutput: false) let response = request.flatMapLatest { page in RxAPIProvider.shared.getPostList(page: page).materialize() } .share(replay: 1) // 处理成功响应,更新列表数据 response.elements() .debug("Response", trimOutput: false) .withLatestFrom(posts.asObservable()) { (newPosts, oldPosts) -> [Post] in return self.pageIndex == 0 ? newPosts : oldPosts + newPosts } .bind(to: posts) .disposed(by: disposeBag) // 根据返回数据更新"是否还有更多数据"的状态 // 这里假设每页预期返回10条数据,可根据你的实际接口规则调整 response.elements() .subscribe(onNext: { [unowned self] newPosts in // 如果返回空数据,或者数据量小于每页预期,标记为无更多数据 self.hasMoreData.value = !newPosts.isEmpty && newPosts.count >= 10 // 如果你接口返回了明确的分页标记(比如isLastPage),建议用这个字段判断: // self.hasMoreData.value = !response.isLastPage }) .disposed(by: disposeBag) // 处理错误响应 response.errors() .bind(to: error) .disposed(by: disposeBag) // 更新加载状态 Observable .merge(request.map{ _ in true }, response.map { _ in false }, error.map { _ in false }) .bind(to: loading) .disposed(by: disposeBag) } }
关键优化点说明
- 新增
hasMoreData状态,从根源上阻止无意义的下一页请求 - 刷新时强制重置
hasMoreData,保证每次下拉刷新都能重新加载分页数据 - 拆分响应处理逻辑,让更新列表、状态判断、错误处理的职责更清晰
- 优先推荐使用接口返回的分页标记(如
isLastPage)来判断是否还有更多数据,比通过数据数量判断更可靠
内容的提问来源于stack exchange,提问作者Zigii Wong
相关产品推荐
相关产品推荐

