使用RxSwift定期调用API

时间:2018-09-13 10:39:25

标签: ios swift rx-swift rxalamofire

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

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

如果messageCountlikesCount值更改,请更新UI。 我尝试了Timer解决方案,但发现它有点混乱,我想要使用RxSwift和RxAlamofire的清洁解决方案。

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

2 个答案:

答案 0 :(得分:3)

欢迎使用StackOverflow!

为此需要很多运算符,我建议您在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时不需要背景/前景簿记。

答案 1 :(得分:3)

CloakedEddy与他的回答非常接近,值得投票。但是他使它变得比必要的更为复杂。 Interval在内部使用DispatchSourceTimer,当应用程序转到后台并返回到前台时,它将自动停止并重新启动。他还记得要抓住错误以阻止流停止展开,所以他做得非常出色。

我假设以下代码位于AppDelegate或高级协调器中。此外,myModelSubjectReplaySubject<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.