SwiftUI Combine实现API分页加载:自动请求下一页至无更多数据
问题
现有SwiftUI + Combine实现的单页API数据请求代码,服务器返回数据包含page、total_pages、hasMore等字段。需求是:当返回数据的hasMore为true时,自动将page_num自增1并发起下一页请求,直到hasMore为false为止。
现有请求代码:
var page_num = 1 guard let url = URL(string: "http://127.0.0.1/api/v4/fetch_records/?page=\(page_num)") else { return } NetworkManager.GET(url: url) .decode(type: GlobalData.self, decoder: JSONDecoder()) .subscribe(on: DispatchQueue.global(qos: .background)) .receive(on: DispatchQueue.main) .sink{ (completion) in } receiveValue: { [weak self] (returnedData) in if returnedData.status == "success" { for record in returnedData.data { print(record) } } }
服务器返回数据结构:
{ "page": 1, "total_pages": 10, "hasMore": true, "status": "success", "data": [{ "id": 23409, "record_id": "P06TYUQZ", "money": "1.00", "date": "2022-10-24" }] }
解决方案
要实现自动分页请求,核心是用Combine的订阅逻辑结合递归调用,同时做好分页状态管理和内存泄漏防护。
完整实现代码
import Combine class RecordFetcher: ObservableObject { private var cancellables = Set<AnyCancellable>() private var currentPage = 1 // 存储所有拉取到的记录,供UI展示 @Published var allRecords: [Record] = [] // 触发全量数据拉取(重置页码) func startFetching() { currentPage = 1 allRecords.removeAll() fetchPage(page: currentPage) } // 单页请求核心方法 private func fetchPage(page: Int) { guard let url = URL(string: "http://127.0.0.1/api/v4/fetch_records/?page=\(page)") else { return } NetworkManager.GET(url: url) .decode(type: GlobalData.self, decoder: JSONDecoder()) .subscribe(on: DispatchQueue.global(qos: .background)) .receive(on: DispatchQueue.main) .sink(receiveCompletion: { completion in if case .failure(let error) = completion { print("分页请求失败:\(error.localizedDescription)") } }, receiveValue: { [weak self] returnedData in guard let self = self else { return } if returnedData.status == "success" { // 追加当前页数据到总集合 self.allRecords.append(contentsOf: returnedData.data) // 判断是否需要继续拉取下一页 if returnedData.hasMore { self.currentPage += 1 self.fetchPage(page: self.currentPage) } else { print("所有数据加载完成,共\(self.allRecords.count)条") } } }) .store(in: &self.cancellables) } } // 对应服务器返回的模型(需确保与JSON结构匹配) struct GlobalData: Decodable { let page: Int let total_pages: Int let hasMore: Bool let status: String let data: [Record] } struct Record: Decodable { let id: Int let record_id: String let money: String let date: String }
关键细节说明
- 状态管理:用
currentPage维护当前请求的页码,allRecords统一存储所有分页数据,方便后续UI绑定展示; - 递归续页:在
receiveValue回调中判断hasMore,若为true则自增页码并再次调用fetchPage发起下一页请求; - 内存安全:使用
[weak self]避免循环引用,通过cancellables集合管理所有订阅,对象销毁时自动取消未完成的请求; - 错误处理:在
receiveCompletion中捕获请求失败的情况,避免异常导致的无限递归。
SwiftUI视图中使用示例
struct RecordsView: View { @StateObject private var fetcher = RecordFetcher() var body: some View { List(fetcher.allRecords, id: \.id) { record in VStack(alignment: .leading) { Text(record.record_id) .font(.headline) Text("金额:\(record.money) | 日期:\(record.date)") .font(.subheadline) .foregroundColor(.secondary) } } .onAppear { fetcher.startFetching() } } }
内容的提问来源于stack exchange,提问作者leo
相关产品推荐
相关产品推荐

