使用 RxSwift 定期调用 API

Posted

技术标签:

【中文标题】使用 RxSwift 定期调用 API【英文标题】:Periodically call an API with RxSwift 【发布时间】:2018-09-13 10:39:25 【问题描述】:

我正在尝试定期(每 10 秒)调用一个返回模型 Json 对象的 API:

struct MyModel  
   var messagesCount: Int?
   var likesCount: Int?

如果 messageCountlikesCount 值发生变化,则更新 UI。 我尝试了 Timer 解决方案,但我发现它有点乱,我想要一个使用 RxSwift 和 RxAlamofire 的更清洁的解决方案。

非常感谢任何帮助,因为我是 Rx 的新手。

【问题讨论】:

仅当应用处于活动状态时?因为否则它将无法正常工作。 您可以使用来自RxSwift Observabletimer。例如Observable<Request>.timer(0, period: 5, scheduler: MainScheduler.instance) @TheTiger 是的,当应用程序处于活动状态时 @kathayatn 你在创建 observable 时是否引用了 RxAlamofire 请求?我不确定您是否可以对其应用Timer 方法。 @Dr34m3r 我不熟悉RxSwift,但是否可以在延迟 10 秒后调用递归函数?在这种情况下它会起作用。 【参考方案1】:

欢迎来到 ***!

这需要很多运算符,我建议在ReactiveX Operator page 上查找它们,每次我忘记某些东西时都会检查它们。

首先,确保 MyModel 符合 Decodable 以便可以从 JSON 响应构造(请参阅 Codable)。

let willEnterForegroundNotification = NotificationCenter.default.rx.notification(.UIApplicationWillEnterForeground)
let didEnterBackgroundNotification = NotificationCenter.default.rx.notification(.UIApplicationDidEnterBackground)

let myModelObservable = BehaviorRelay<MyModel?>(value: nil)

willEnterForegroundNotification
    // discard the notification object
    .map  _ in () 
    // emit an initial element to trigger the timer immediately upon subscription
    .startWith(())
    .flatMap  _ in 
        // create an interval timer which stops emitting when the app goes to the background
        return Observable<Int>.interval(10, scheduler: MainScheduler.instance)
            .takeUntil(didEnterBackgroundNotification)
    
    .flatMapLatest  _ in 
        return RxAlamofire.requestData(.get, yourUrl)
            // get Data object from emitted tuple
            .map  $0.1  
            // ignore any network errors, otherwise the entire subscription is disposed
            .catchError  _ in .empty()  
     
    // leverage Codable to turn Data into MyModel
    .map  try? JSONDecoder().decode(MyModel.self, from: $0)  
    // operator from RxOptional to turn MyModel? into MyModel
    .filterNil() 
    .bind(to: myModelObservable)
    .disposed(by: disposeBag)

然后,您可以继续将数据流传输到您的 UI 元素中。

myModelObservable
    .map  $0.messagesCount 
    .map  "\($0) messages" 
    .bind(to: yourLabel.rx.text 
    .disposed(by: disposeBag)

我没有运行此代码,因此此处可能存在一些拼写错误/缺少转换,但这应该会为您指明正确的方向。随时要求澄清。如果真的是 Rx 的新手,我建议通过 Getting Started guide。这很棒! Rx很强大,但是我花了一段时间才掌握。

编辑

正如@daniel-t 指出的,使用Observable&lt;Int&gt;.interval 时不需要后台/前台簿记。

【讨论】:

您实际上并不需要前景背景的东西。当应用程序在后台时计时器将自动停止,并在它回到前台时重新启动。否则,干得好。 这正是我所需要的,非常感谢。在我的情况下,通过观察应用程序的状态(前景、背景)是必要的,因为@CloakedEddy 提供的代码 ReplaySubject 发出了应用程序在后台时跳过的所有事件。使用 Daniel 的解决方案,当应用程序处于后台时不会触发计时器,并且不会在前台处理大量事件。【参考方案2】:

CloakedEddy 的回答非常接近,值得点赞。然而,他使它变得比必要的复杂一些。 Interval 在内部使用 DispatchSourceTimer,当应用程序进入后台并返回前台时,它将自动停止并重新启动。他在记住捕获错误以阻止流展开方面也做得很好。

我假设以下代码位于 AppDelegate 或高级协调器中。此外,myModelSubject 是一个 ReplaySubject&lt;MyModel&gt;(使用:ReplaySubject&lt;MyModel&gt;.create(bufferSize: 1) 创建它,应该放置在视图控制器可以访问或传递给视图控制器的某个位置。

Observable<Int>.interval(10, scheduler: MainScheduler.instance) // fire at 10 second intervals.
    .flatMapLatest  _ in
        RxAlamofire.requestData(.get, yourUrl) // get data from the server.
            .catchError  _ in .empty()    // don't let error escape.
    
    .map  $0.1  // this assumes that alamofire returns `(URLResponse, Data)`. All we want is the data.
    .map  try? JSONDecoder().decode(MyModel.self, from: $0)  // this assumes that MyModel is Decodable
    .filter  $0 != nil  // filter out nil values
    .map  $0!  // now that we know it's not nil, unwrap it.
    .bind(to: myModelSubject) // store the value in a global subject that view controllers can subscribe to.
    .disposed(by: bag) // always clean up after yourself.

【讨论】:

以上是关于使用 RxSwift 定期调用 API的主要内容,如果未能解决你的问题,请参考以下文章

在 RxSwift 中重新启动可观察间隔的正确方法

使用 RxSwift 同步异步网络调用

使用 RxSwift 进行分页 API 调用

如何使用 RxSwift 和 alamofire 获得嵌套 api 调用的响应?

使用 RxSwift Observables 进行递归调用

使用 RxSwift 只执行一次 API 调用