使用 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?
如果 messageCount
或 likesCount
值发生变化,则更新 UI。
我尝试了 Timer 解决方案,但我发现它有点乱,我想要一个使用 RxSwift 和 RxAlamofire 的更清洁的解决方案。
非常感谢任何帮助,因为我是 Rx 的新手。
【问题讨论】:
仅当应用处于活动状态时?因为否则它将无法正常工作。 您可以使用来自RxSwift Observable
的timer
。例如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<Int>.interval
时不需要后台/前台簿记。
【讨论】:
您实际上并不需要前景背景的东西。当应用程序在后台时计时器将自动停止,并在它回到前台时重新启动。否则,干得好。 这正是我所需要的,非常感谢。在我的情况下,通过观察应用程序的状态(前景、背景)是必要的,因为@CloakedEddy 提供的代码 ReplaySubject 发出了应用程序在后台时跳过的所有事件。使用 Daniel 的解决方案,当应用程序处于后台时不会触发计时器,并且不会在前台处理大量事件。【参考方案2】:CloakedEddy 的回答非常接近,值得点赞。然而,他使它变得比必要的复杂一些。 Interval 在内部使用 DispatchSourceTimer,当应用程序进入后台并返回前台时,它将自动停止并重新启动。他在记住捕获错误以阻止流展开方面也做得很好。
我假设以下代码位于 AppDelegate 或高级协调器中。此外,myModelSubject
是一个 ReplaySubject<MyModel>
(使用:ReplaySubject<MyModel>.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的主要内容,如果未能解决你的问题,请参考以下文章