如何使用 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?

使用 RxSwift 将 Alamofire 请求绑定到表视图

结合 Alamofire 和 RxSwift

RxSwift+Alamofire 自定义映射器错误处理