如何使用 RxSwift 和 Alamofire 使用分页?
Posted
技术标签:
【中文标题】如何使用 RxSwift 和 Alamofire 使用分页?【英文标题】:How to use pagination using RxSwift and Alamofire? 【发布时间】:2018-08-31 12:21:40 【问题描述】:我正在尝试使用带有 alamofire 和 rxswift 的 api。我已经编写了方法,但是观察者的 onNext 只被调用一次。我正在尝试通过递归调用来做到这一点。这有什么问题? Api 将根据时间戳一次返回 10 个对象。所以我正在检查刚刚返回的数组是否包含 10 个对象。如果是,那么还有更多,如果不是,那就结束了。
func fetchPersonalization(fromList:[Personalization],timeStamp:Int) -> Observable<PersonalizationContainer>
let dictHeader = ["Accept":"application/json","regid" : pushtoken , "os" : "ios" , "token" : token , "App-Version" : "1324" , "Content-Type" : "application/json"]
return fetchPersonalizationUtil(dictHeader: dictHeader, timeStamp: timeStamp)
.flatMap (perList) -> Observable<PersonalizationContainer> in
let persoList:[Personalization] = perList.list
let finalList = fromList + persoList
if(persoList.count==10)
let newTimeStamp = persoList.last!.lastModifiedAt! - 1
return Observable.merge(Observable.just(PersonalizationContainer(l: finalList, d: perList.data)),
self.fetchPersonalization(fromList:finalList,timeStamp: newTimeStamp)
)
//self.fetchPersonalization(fromList:finalList,timeStamp: newTimeStamp)
else
return Observable.just(PersonalizationContainer(l: finalList, d: Data()))
func fetchPersonalizationUtil(dictHeader:[String:String],timeStamp:Int) -> Observable<PersonalizationContainer>
return Observable<PersonalizationContainer>.create( (observer) -> Disposable in
Alamofire.request("https://mranuran.com/api/hubs/personalization/laterthan/\(timeStamp)/limit/10/" ,headers: dictHeader).responseData response in
if let json = response.result.value
//print("HUBs JSON: \(json)")
do
let list = try JSONDecoder().decode([Personalization].self, from: json)
let pContainer = PersonalizationContainer(l: list, d: json)
print("ANURAN \(list[0].name)")
observer.onNext(pContainer)
observer.onCompleted()
catch
print(error)
observer.onError(error)
else
observer.onError(response.result.error!)
return Disposables.create()
)
我在 onNext 方法上设置了一个断点,它似乎只被调用了一次。在他们的官方 github repo 中坚持了几个小时和 RxSwift 的 GithubRepo 示例,我无法弄清楚他们在做什么。我的流程有什么问题?
【问题讨论】:
onCompleted 将停止观察者发射。尝试删除它,只有在完成后才调用它,不要期待更多的调用。 【参考方案1】:我之前用 Promises 写过这篇文章,这里是使用 Singles。
你传入:
-
用于进行第一次网络调用的种子。
pred 将获得最近一次调用的结果,并产生一个参数以进行下一次网络调用,如果完成,则为 nil。 (如果需要再次调用,您可以在这里检查计数并返回下一个时间戳。)
进行网络调用的生产者。
它最终返回一个带有所有结果数组的 Single。如果任何内部网络调用出错,就会出错。
func accumulateWhile<T, U>(seed: U, pred: @escaping (T) -> U?, producer: @escaping (U) -> Single<T>) -> Single<[T]>
return Single.create observer in
var disposable = CompositeDisposable()
var accumulator: [T] = []
let lock = NSRecursiveLock()
func loop(_ u: U)
let product = producer(u)
let subDisposable = product.subscribe event in
lock.lock(); defer lock.unlock()
switch event
case let .success(value):
accumulator += [value]
if let u = pred(value)
loop(u)
else
observer(.success(accumulator))
case let .error(error):
observer(.error(error))
_ = disposable.insert(subDisposable)
loop(seed)
return disposable
我认为锁实际上没有必要,但我把它放在以防万一。
【讨论】:
【参考方案2】:我基于@Daniel T.'s answer 进行了改进,添加了下一页加载触发器。在类似情况下,只有当用户滚动到UITableView
的底部时才应该加载下一页时,这很有用。
第一页在订阅后立即加载,每个后续页面在收到nextPageTrigger
参数中的信号后立即加载
示例用法:
let contents = loadPagesLazily(
seed: 1,
requestProducer: (pageNumber: Int) -> Single<ResponseContainer<[Content]>> in
return dataSource.loadContent(page: Id, pageSize: 20)
,
nextKeySelector: (responseContainer: ResponseContainer<[Content]>) -> Meta? in
let hasMorePages = responseContainer.meta.currentPage < responseContainer.meta.lastPage
return hasMorePages ? responseContainer.meta.currentPage + 1 : nil
,
nextPageTrigger: loadMoreTrigger
)
return contents
.scan([]], accumulator: (accumulator, nextPageContainer) -> SearchResults in
accumulator + nextPageContainer.data
)
参数:
seed
- 首页加载信息PageKey
requestProducer
-
将每个PageKey
转换为加载Single
的页面
nextKeySelector
- 根据数据创建下一页的登录信息
在每个页面中检索到来自requestProducer
调用。返回
nil
这里如果没有下一页。
nextPageTrigger
- 之后
接收第一页每个后续页面仅在之后返回
在这个 observable 中接收到 .next
信号/
func loadPagesLazily(
seed: PageKey,
requestProducer: @escaping (PageKey) -> Single<Page>,
nextKeySelector: @escaping (Page) -> PageKey?,
nextPageTrigger: Observable<Void>
) -> Observable<Page>
return requestProducer(seed)
.asObservable()
.flatMap( (response) -> Observable<Page> in
let nextPageKey = nextKeySelector(response)
let nextPageLoader: Observable<Page> = nextPageKey
.map (meta) -> Observable<Page> in
nextPageTrigger.take(1)
.flatMap (_) -> Observable<Page> in
loadPagesLazily(
seed: meta,
requestProducer: requestProducer,
nextKeySelector: nextKeySelector,
nextPageTrigger: nextPageTrigger
)
?? Observable.empty()
// Concatenate self and next page recursively
return Observable
.just(response)
.concat(nextPageLoader)
)
【讨论】:
以上是关于如何使用 RxSwift 和 Alamofire 使用分页?的主要内容,如果未能解决你的问题,请参考以下文章
如何使用 RxSwift 和 alamofire 获得嵌套 api 调用的响应?
Alamofire/RxSwift 如何在状态码 401 上自动刷新令牌和重试请求
如何使用 RxSwift 和 Alamofire 库调用来自另一个 API 的响应的 API?