使用 RxSwift 定期调用 API
Periodically call an API with RxSwift
我正在尝试定期(每 10 秒)调用一个 API 那个 returns 一个 Json 模型对象 :
struct MyModel {
var messagesCount: Int?
var likesCount: Int?
}
如果 messageCount
或 likesCount
值发生变化,则更新 UI。
我尝试了 Timer 解决方案,但我发现它有点乱,我想要一个使用 RxSwift 和 RxAlamofire 的更干净的解决方案。
非常感谢任何帮助,因为我是 Rx 的新手。
欢迎使用 Whosebug!
这需要很多运算符,我建议在 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 { [=10=].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: [=10=]) } }
// operator from RxOptional to turn MyModel? into MyModel
.filterNil()
.bind(to: myModelObservable)
.disposed(by: disposeBag)
然后,您可以继续数据流到您的 UI 元素中。
myModelObservable
.map { [=11=].messagesCount }
.map { "\([=11=]) messages" }
.bind(to: yourLabel.rx.text }
.disposed(by: disposeBag)
我没有 运行 此代码,因此此处可能有一些 typos/missing 转换,但这应该为您指明了正确的方向。随时要求澄清。如果 真的 是 Rx 的新手,我建议阅读 Getting Started guide。这很棒! Rx很强大,但我花了一段时间才掌握。
编辑
正如@daniel-t 所指出的,使用 Observable<Int>.interval
时不需要 background/foreground 簿记。
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 { [=10=].1 } // this assumes that alamofire returns `(URLResponse, Data)`. All we want is the data.
.map { try? JSONDecoder().decode(MyModel.self, from: [=10=]) } // this assumes that MyModel is Decodable
.filter { [=10=] != nil } // filter out nil values
.map { [=10=]! } // 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.
我正在尝试定期(每 10 秒)调用一个 API 那个 returns 一个 Json 模型对象 :
struct MyModel {
var messagesCount: Int?
var likesCount: Int?
}
如果 messageCount
或 likesCount
值发生变化,则更新 UI。
我尝试了 Timer 解决方案,但我发现它有点乱,我想要一个使用 RxSwift 和 RxAlamofire 的更干净的解决方案。
非常感谢任何帮助,因为我是 Rx 的新手。
欢迎使用 Whosebug!
这需要很多运算符,我建议在 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 { [=10=].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: [=10=]) } }
// operator from RxOptional to turn MyModel? into MyModel
.filterNil()
.bind(to: myModelObservable)
.disposed(by: disposeBag)
然后,您可以继续数据流到您的 UI 元素中。
myModelObservable
.map { [=11=].messagesCount }
.map { "\([=11=]) messages" }
.bind(to: yourLabel.rx.text }
.disposed(by: disposeBag)
我没有 运行 此代码,因此此处可能有一些 typos/missing 转换,但这应该为您指明了正确的方向。随时要求澄清。如果 真的 是 Rx 的新手,我建议阅读 Getting Started guide。这很棒! Rx很强大,但我花了一段时间才掌握。
编辑
正如@daniel-t 所指出的,使用 Observable<Int>.interval
时不需要 background/foreground 簿记。
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 { [=10=].1 } // this assumes that alamofire returns `(URLResponse, Data)`. All we want is the data.
.map { try? JSONDecoder().decode(MyModel.self, from: [=10=]) } // this assumes that MyModel is Decodable
.filter { [=10=] != nil } // filter out nil values
.map { [=10=]! } // 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.