Lets say I have a function that takes in two arguments - Observable<String?> and Observable<Person?> and returns Observable
This is my sample code below:
return Observable.combineLatest(self.id, personStream)
map { [weak self] (id,person) -> Person? in
guard let person = person as? Person else {
return nil
}
if let id = id {
// How do I return this updated person
self?.updatePersonDetails(for: person, completion: { (updatedPerson) in
return updatedPerson
})
} else {
return person
}
}
How do I return this updated person inside this mapping function?
Update:
public func updatePerson(personStream: Observable<Person>) -> Observable<Person> {
return Observable.combineLatest(
self.id,
personStream
).flatMap { [weak self] (id,person) -> Observable<Person?> in
guard let person = person as? Double else {
return Observable.just(nil)
}
if let id = id {
// How do I return this updated person
return Observable.create { [weak self] observer in
self?.updatePersonDetails(for: person, completion: { (updatedPerson) in
observer.on(.next(updatedPerson))
observer.on(.completed)
})
return Disposables.create()
}
} else {
return Observable.just(person)
}
}
}
The above function is called as below:
let personUpdateStream = personService.updatePerson(personStream: personDataStream)
Observable.combineLatest(personUpdateStream, nameStream,profileEnabledStream)
.subscribe(onNext: { [weak self] (person, name, isEnabled) in
print(name)
self?.updatePersonView(person: person, name: name, isLocked: !isEnabled)
})
.disposed(by: disposeBag)
Issue: The block inside combineLatest with the print(name) never runs. May I know whats the issue here?
Description of personUpdateStream
personUpdateStream RxSwift.FlatMap<(String?, Person?), RxSwift.Observable<Person?>> 0x0000604001e5af70
You should use flatMap instead of map and to integrate your non Rx call of updatePersonDetails you should use Observable.create.
Your final code should look something similar to:
return Observable.combineLatest(
self.id,
personStream
).flatMap { [weak self] (id,person) -> Observable<Person?> in
guard let person = person as? Double else {
return Observable.just(nil)
}
if let id = id {
// How do I return this updated person
return Observable.create { [weak self] observer in
self?.updatePersonDetails(for: person, completion: { (updatedPerson) in
observer.on(.next(updatedPerson))
observer.on(.completed)
})
return Disposables.create()
}
} else {
return Observable.just(person)
}
}
Related
We have three states.How can we test(with unit tests) our class which generates random state every 5 seconds, and which can not generate the same state twice in a row? The code of our random generator class is below
`
final class StateRandomGenerator: RandomGeneratorProtocol {
private var sourceObservable: Disposable?
private(set) var previousValue: Int?
var generatedValue: PublishSubject = PublishSubject()
init(_ interval: RxTimeInterval,_ scheduler: SchedulerType = MainScheduler.instance) {
sourceObservable = Observable<Int>
.interval(interval, scheduler: scheduler)
.flatMap { [unowned self] _ in self.generateRandom()}
.compactMap { state in
return state?.description
}
.subscribe(onNext: { [weak self] description in
self?.generatedValue.onNext(description)
})
}
func generateRandom() -> Observable<ConnectionState?> {
return Observable.create { [weak self] observer in
var randomNumber = Int.random(in: 0..<ConnectionState.count)
guard let previousValue = self?.previousValue else {
let value = ConnectionState(rawValue: randomNumber)
self?.previousValue = randomNumber
observer.onNext(value)
return Disposables.create()
}
while randomNumber == previousValue {
randomNumber = Int.random(in: 0..<ConnectionState.count)
}
self?.previousValue = randomNumber
let value = ConnectionState(rawValue: randomNumber)
observer.onNext(value)
return Disposables.create()
}
}
enum ConnectionState: Int {
case error
case connecting
case established
var description: String {
switch self {
case .connecting:
return "It is connecting"
case .error:
return "There is an error"
case .established:
return "Thе connection is established"
}
}
}
`
You can't successfully unit test your class because it doesn't halt. It just pegs the CPU and chews up memory until the system is finally starved and crashes.
Below is a working and tested Observable that does what you want... The test creates 100,000 ConnectionStates and then checks to ensure that no two adjacent are identical.
The main logic of the function is the closure passed to map which grabs all the cases and filters out the previous case. A random element is chosen from the remainder.
It would be pretty easy to make this generic across any enum I expect. I'll leave that as an exercise for the reader.
func stateRandom(_ interval: RxTimeInterval,_ scheduler: SchedulerType = MainScheduler.instance) -> Observable<ConnectionState> {
let previous = BehaviorRelay<ConnectionState?>(value: nil)
return Observable<Int>.interval(interval, scheduler: scheduler)
.withLatestFrom(previous)
.map { ConnectionState.allExcept($0) }
.flatMap { Observable.just($0.randomElement()!) }
.do(onNext: { previous.accept($0) })
}
extension CaseIterable where Self: Equatable {
static func allExcept(_ value: Self?) -> [Self] {
allCases.filter { $0 != value }
}
}
enum ConnectionState: CaseIterable, Equatable {
case error
case connecting
case established
}
class Tests: XCTestCase {
func test() throws {
let scheduler = TestScheduler(initialClock: 0)
let result = scheduler.start { stateRandom(.seconds(1), scheduler).take(100000) }
for (prev, current) in zip(result.events, result.events.dropFirst()) {
XCTAssertNotEqual(prev.value, current.value)
}
}
}
I've got a function that will return an observable that will emit an enum based on what stage it is currently on. The enum basically goes like this:
enum ViewModelAction {
case inputEnabled(isEnabled: Bool)
case loadingIndicatorShown(isShown: Bool)
case errorMessageShownIfAvailable(error: Error?)
case loadUIData(with entity: Entity)
case startSession(for entity: Entity)
case endSession(for entity: Entity)
case loadEndUIData(with entity: Entity)
}
Now what I want the function do is like so:
Function start with input params -> go to 2
Do:
Emit disable input from UI
Emit show loading indicator
Call API to create session based on input params -> API returns session data -> go to 3
Do:
If success -> go to 4
If error:
Emit enable input from UI
Emit hide loading indicator
Emit show error with the error -> Complete stream
Do:
Loop and call API to poll the entity based on the session with the status of READY:
If success with READY status -> Emit load UI data with entity -> go to 5
If success with non READY status -> loop again / back to 4
If error -> Emit show error with the error -> Loop again / back to 4
Do:
Call API to start session using the entity -> API returns session data -> go to 6
Do:
If success:
Emit enable input from UI
Emit hide loading indicator
Emit start session for entity -> Complete stream
If error:
Emit enable input from UI
Emit hide loading indicator
Emit show error with the error -> Complete stream
So in a way, for a successful run, it will emit this exact sequence:
Observable.merge(
.just(ViewModelAction.inputEnabled(isEnabled: false)),
.just(ViewModelAction.loadingIndicatorShown(isShown: true)),
.just(ViewModelAction.loadUIData(with: entity)),
.just(ViewModelAction.inputEnabled(isEnabled: true)),
.just(ViewModelAction.loadingIndicatorShown(isShown: false)),
.just(ViewModelAction.startSession(for: entity)),
.complete()
)
I have actually done this, but I was thinking that it is not that clear and very complicated.
protocol SessionHandlerProtocol {
func create(for userId: String) -> Observable<SessionData>
func start(session: SessionData, entity: Entity) -> Observable<Void>
}
protocol EntityPollerProtocol {
var entity: Observable<Entity?> { get }
func startPolling(for session: SessionData) //Will poll and emit to entity observable
finc stopPolling()
}
class StartSessionStrategy {
private let sessionHandler: SessionHandlerProtocol
private let entityPoller: EntityPollerProtocol
init(sessionHandler: SessionHandlerProtocol,
entityPoller: EntityPollerProtocol) {
self.sessionHandler = sessionHandler
self.entityPoller = entityPoller
}
func handleSession(with userId: String) -> Observable<ViewModelAction> {
let initialUIObservable =
Observable.from(ViewModelAction.inputEnabled(isEnabled: false),
ViewModelAction.loadingIndicatorShown(isShown: true))
let sharedCreateSession =
sessionHandler.create(for: userID).materialize.share()
let createSessionError =
sharedCreateSession
.flatMapLatest {
switch $0 {
case .error(let error):
return Observable.merge(
.just(ViewModelAction.inputEnabled(isEnabled: true)),
.just(ViewModelAction.loadingIndicatorShown(isShown: false)),
.just(ViewModelAction.errorMessageShownIfAvailable(error: error)),
.complete()
)
default:
return .never()
}
let createSessionSuccess =
sharedCreateSession
.flatMapLatest {
switch $0 {
case .next(let element):
return .just(element)
default:
return .never()
}
let sharedEntityPoller =
createSessionSuccess
.do(onNext: { [weak self] in self?.entityPoller.startPolling(for: $0) })
.withLatestFrom(entityPoller.entity) { return ($0, $1) }
.materialize()
.share()
let entityPollerError =
sharedEntityPoller
.flatMapLatest {
switch $0 {
case .error(let error):
return .just(ViewModelAction.errorMessageShownIfAvailable(error: error))
default:
return .never()
}
let entityPollerSuccessWithReadyStatus =
sharedEntityPoller
.filter { (_, entity) entity.status = .ready }
.flatMapLatest {
switch $0 {
case .next(let element):
return .just(element)
default:
return .never()
}
.do(onNext: { [weak self] _ in self?.stopPolling() })
let doOnEntityPollerSuccessWithReadyStatus =
entityPollerSuccessWithReadyStatus
.map { return ViewModelAction.loadUIData(with: $0.1) }
let sharedStartSession =
entityPollerSuccessWithReadyStatus
.flatMapLatest { [weak self] (session, entity) in
self?.sessionHandler
.start(session: userID, entity: entity)
.map { return (session, entity) }
}.materialize.share()
let startSessionError =
sharedStartSession
.flatMapLatest {
switch $0 {
case .error(let error):
return Observable.merge(
.just(ViewModelAction.inputEnabled(isEnabled: true)),
.just(ViewModelAction.loadingIndicatorShown(isShown: false)),
.just(ViewModelAction.errorMessageShownIfAvailable(error: error)),
.complete()
)
default:
return .never()
}
let startSessionSuccess =
sharedStartSession
.flatMapLatest {
switch $0 {
case .next(let element):
return Observable.merge(
.just(ViewModelAction.inputEnabled(isEnabled: true)),
.just(ViewModelAction.loadingIndicatorShown(isShown: false)),
.just(ViewModelAction.startSession(for: element.1)),
.complete
)
default:
return .never()
}
return Observable.merge(
initialUIObservable,
createSessionError,
entityPollerError,
doOnEntityPollerSuccessWithReadyStatus,
startSessionError,
startSessionSuccess
)
}
}
As you can see, the function is pretty big and not that clear. Do you have a suggestion how to refactor this into a cleaner code? Thanks.
If you start with a flow chart, you will end up with an imperative solution. Instead consider each side effect independently. Make each observable sequence a declaration of what causes that side effect.
The essence of your code is this (Note, I wrapped up your entity poller into a more reasonable interface. That wrapper is shown later):
let errors = PublishSubject<Error>()
let session = sessionHandler.create(for: userId)
.catch { errors.onSuccess($0); return .empty() }
.share()
let entity = session
.flatMapLatest { [entityPoller] in
entityPoller($0)
.catch { errors.onSuccess($0); return .empty() }
}
.share()
let started = entity
.compactMap { $0 }
.filter { $0.status == .ready }
.withLatestFrom(session) { ($1, $0) }
.flatMapLatest { [sessionHandler] session, entity in
sessionHandler.start(session: session, entity: entity)
.catch { errors.onSuccess($0); return .empty() }
}
.take(1)
.share()
Everything else is just notifications. I think showing the above, cleanly and without complications, is a huge simplification and will aid in other's understanding your code (including future you.)
Here is what I ended up with, including the wrapper around the poller I mentioned above:
class StartSessionStrategy {
private let sessionHandler: SessionHandlerProtocol
private let entityPoller: (SessionData) -> Observable<Entity?>
init(sessionHandler: SessionHandlerProtocol, poller: EntityPollerProtocol) {
self.sessionHandler = sessionHandler
self.entityPoller = entityPolling(poller)
}
func handleSession(with userId: String) -> Observable<ViewModelAction> {
// the fundamental operations as above:
let errors = PublishSubject<Error>()
let session = sessionHandler.create(for: userId)
.catch { errors.onSuccess($0); return .empty() }
.share()
let entity = session
.flatMapLatest { [entityPoller] in
entityPoller($0)
.catch { errors.onSuccess($0); return .empty() }
}
.share()
let started = entity
.compactMap { $0 }
.filter { $0.status == .ready }
.withLatestFrom(session) { ($1, $0) }
.flatMapLatest { [sessionHandler] session, entity in
sessionHandler.start(session: session, entity: entity)
.catch { errors.onSuccess($0); return .empty() }
}
.take(1)
.share()
// now go through all the notifications:
// input is disabled at start, then enabled once started or if an error occurs
let inputEnabled = Observable.merge(
started,
errors.take(until: started).map(to: ())
)
.map(to: ViewModelAction.inputEnabled(isEnabled: true))
.startWith(ViewModelAction.inputEnabled(isEnabled: false))
.take(2)
// show the loading indicator at start, remove it once started or if an error occurs
let loadingIndicator = Observable.merge(
started,
errors.take(until: started).map(to: ())
)
.map(to: ViewModelAction.loadingIndicatorShown(isShown: false))
.startWith(ViewModelAction.loadingIndicatorShown(isShown: true))
.take(2)
// emit the loadUIData if an entity is ready.
let loadUIData = entity
.compactMap { $0 }
.filter { $0.status == .ready }
.map { ViewModelAction.loadUIData(entity: $0) }
.take(1)
// emit the startSession event once the session starts.
let startSession = started
.withLatestFrom(entity.compactMap { $0 })
.map { entity in ViewModelAction.startSession(entity: entity) }
.take(until: errors)
return Observable.merge(
inputEnabled,
loadingIndicator,
errors
.take(until: started)
.map { ViewModelAction.errorMessageShownIfAvailable(error: $0) }, // emit an error message for all errors.
loadUIData,
startSession
)
}
}
func entityPolling(_ poller: EntityPollerProtocol) -> (SessionData) -> Observable<Entity?> {
{ session in
Observable.create { observer in
let disposable = poller.entity
.subscribe(observer)
poller.startPolling(for: session)
return Disposables.create {
disposable.dispose()
poller.stopPolling()
}
}
}
}
extension ObserverType {
func onSuccess(_ element: Element) -> Void {
onNext(element)
onCompleted()
}
}
extension ObservableType {
func map<T>(to: T) -> Observable<T> {
return map { _ in to }
}
}
New to RxSwift here. I have a (MVVM) view model that represents a Newsfeed-like page, what's the correct way to subscribe to change in data model's properties? In the following example, startUpdate() constantly updates post. The computed properties messageToDisplay and shouldShowHeart drives some UI event.
struct Post {
var iLiked: Bool
var likes: Int
...
}
class PostViewModel: NSObject {
private var post: Post
var messageToDisplay: String {
if post.iLiked { return ... }
else { return .... }
}
var shouldShowHeart: Bool {
return iLiked && likes > 10
}
func startUpdate() {
// network request and update post
}
...
}
It seems to me in order to make this whole thing reactive, I have to turn each properties of Post and all computed properties into Variable? It doesn't look quite right to me.
// Class NetworkRequest or any name
static func request(endpoint: String, query: [String: Any] = [:]) -> Observable<[String: Any]> {
do {
guard let url = URL(string: API)?.appendingPathComponent(endpoint) else {
throw EOError.invalidURL(endpoint)
}
return manager.rx.responseJSON(.get, url)
.map({ (response, json) -> [String: Any] in
guard let result = json as? [String: Any] else {
throw EOError.invalidJSON(url.absoluteString)
}
return result
})
} catch {
return Observable.empty()
}
}
static var posts: Observable<[Post]> = {
return NetworkRequest.request(endpoint: postEndpoint)
.map { data in
let posts = data["post"] as? [[String: Any]] ?? []
return posts
.flatMap(Post.init)
.sorted { $0.name < $1.name }
}
.shareReplay(1)
}()
class PostViewModel: NSObject {
let posts = Variable<[Post]>([])
let disposeBag = DisposeBag()
override func viewDidLoad() {
super.viewDidLoad()
posts
.asObservable()
.subscribe(onNext: { [weak self] _ in
DispatchQueue.main.async {
//self?.tableView?.reloadData() if you want to reload all tableview
self.tableView.insertRows(at: [IndexPath], with: UITableViewRowAnimation.none) // OR if you want to insert one or multiple rows.
//OR update UI
}
})
.disposed(by: disposeBag)
posts.asObservable()
.bind(to: tableView.rx.items) { [unowned self] (tableView: UITableView, index: Int, element: Posts) in
let cell = tableView.dequeueReusableCell(withIdentifier: "postCell") as! PostCell
//for example you need to update the view model and cell with textfield.. if you want to update the ui with a cell then use cell.button.tap{}. hope it works for you.
cell.textField.rx.text
.orEmpty.asObservable()
.bind(to: self.posts.value[index].name!)
.disposed(by: cell.disposeBag)
return cell
}
startDownload()
}
}
func startDownload {
posts.value = NetworkRequest.posts
}
If you want to change anything then use subscribe, bind, concat .. There are many methods and properties which you can use.
The following code works:
let provider = RxMoyaProvider<MyAPI>( stubClosure: MoyaProvider.delayedStub(3))
provider
.request(.studentSearch(query: ""))
.retry(3)
.observeOn(MainScheduler.instance)
.asObservable()
.mapJSON()
.map { respJSON in
guard let studentsJsonArray = JSON(respJSON)["students"].array else {
throw APIError.wrongJSONParsing
}
return studentsJsonArray.map {
guard let students = Student.fromJSON($0) else {
fatalError("Invalid Student Object")
}
return students
} as [Student]
}
.subscribe(onNext: {
print($0)
}, onCompleted: {
print($0) // This one is being called.
})
.disposed(by: rx.disposeBag)
The method onCompleted is being called in the above code But not in the following one.
I am trying to do it with refresh trigger like the one in the UITableView for refreshing the content. I want to load the contents on start so I use startWith(()) in the following code in my ViewModel
let results: Driver<[Student]>
var refreshTrigger = PublishSubject<Void>()
results = refreshTrigger
.startWith(())
.do(onNext: {
execute.value = true
})
.flatMapLatest {
provider
.request(.studentSearch(query: ""))
.retry(3)
.observeOn(MainScheduler.instance)
.asObservable()
}
.mapJSON()
.map { respJSON in
guard let studentsJsonArray = JSON(respJSON)["students"].array else {
throw APIError.wrongJSONParsing
}
return studentsJsonArray.map {
guard let students = Student.fromJSON($0) else {
fatalError("Invalid Student Object")
}
return students
}
}
.do(onNext: {
items.value = $0
execute.value = false
noResults.value = items.value.isEmpty
}, onCompleted: {
print($0)
})
.asDriver(onErrorJustReturn: [])
and in the controller I call following in viewdidload.
viewModel
.results
.asObservable()
.map { StudentGroup(header: "Follower", items: $0) }
.subscribe(onNext: {
print($0)
}, onCompleted: {
print($0) // This is not being called.
})
.disposed(by: rx.disposeBag)
Here onCompleted is not being called. I don't know the reason why?. Please help me out.
flatMapLatest will not send an onCompleted event unless its input (the PublishSubject in your case) completes.
The Moya requests send their own onCompleted events alright, but those are filtered out when flatMapLatest merges the results.
In other words, there is a long-lived subscription from the PublishSubject to the data source which doesn't complete, and it's not supposed to, either. (edited)
(Otherwise you'd lose the refresh functionality after the first load.)
I got help from Zsolt Varadi from RxSwift Slack channel.
here is the solution
results = refreshTrigger
.startWith(())
.do(onNext: {
execute.value = true
})
.flatMapLatest {
provider
.request(.studentSearch(query: ""))
.retry(3)
.observeOn(MainScheduler.instance)
.asObservable()
.mapJSON()
.map { respJSON in
guard let studentsJsonArray = JSON(respJSON)["students"].array else {
throw APIError.wrongJSONParsing
}
return studentsJsonArray.map {
guard let students = Student.fromJSON($0) else {
fatalError("Invalid Student Object")
}
return students
} as [Student]
}
.map { StudentGroup(header: "Follower", items: $0) }
.toArray()
.catchErrorJustReturn([])
.do(onNext: {
studentGroups.value = $0
execute.value = false
guard let isEmpty = studentGroups.value.first?.items.isEmpty else {
return
}
noResults.value = isEmpty
})
}
.asDriver(onErrorJustReturn: [])
I studying rxSwift, and I want to do service for the interaction of c longpolling server to this service imitating a permanent connection. I wrote it, but it seems to me, is not that the decision could have been done better? Is it possible to somehow repeat the Observable, regardless of the error, and depending on longpoll server response.
Can anyone can share the solution? Or help with advice? How it is better to organize? I would like to see a better solution, since only began studying rxswift
class LongPollingService {
public var messageReciver: PublishSubject<EventProtocol> = PublishSubject<EventProtocol>()
private let transport = DefaultTransport()
private let disposeBag = DisposeBag()
private var currentRequestInfo = Variable<LongpollingServerInfo?>(nil)
private var currentRequestDisposable: Disposable?
private var currentLongpollingConnection: Disposable? // Subsribee for request server info
private var eventListener : Disposable?
private var currentReqursiveConnection: Disposable? // Subscriber for event listener from longpoll server
func startObservableEvents() {
getServerConnection()
subscribeServerInfo()
//testing listen events
eventListener = messageReciver.showMessagesInDebugMode().subscribe()
eventListener?.addDisposableTo(disposeBag)
}
func disconnect() {
currentRequestDisposable?.dispose()
currentLongpollingConnection?.dispose()
currentReqursiveConnection?.dispose()
}
private func subscribeServerInfo() {
currentLongpollingConnection = currentRequestInfo
.asObservable()
.filter({$0 != nil})
.subscribe(onNext: { [weak self] (info) in
guard let sSelf = self else { return }
sSelf.subscribeToEvents(timeStamp: info!.ts)
})
currentLongpollingConnection?.addDisposableTo(disposeBag)
}
private func subscribeToEvents(timeStamp: TimeInterval) {
if let serverInfo = currentRequestInfo.value {
currentReqursiveConnection?.dispose()
currentReqursiveConnection = getEventsFromLongpollServer(serverInfo: serverInfo, with: timeStamp)
.flatMap(parseUpdates)
.flatMap(reciveEvents)
.showErrorsSwiftMessagesInDebugMode()
.subscribe(onNext: { [weak self] updates in
guard let sSelf = self else { return }
sSelf.subscribeToEvents(timeStamp: updates)
},
onError: { [weak self] error in
guard let sSelf = self else { return }
if let error = error as? LongPollError {
switch error {
case .olderHistory(let ts): sSelf.subscribeToEvents(timeStamp: ts)
default: sSelf.getServerConnection()
}
}
})
currentReqursiveConnection?.addDisposableTo(disposeBag)
}
}
private func getServerConnection() {
//get longpolling server info for connection.
currentRequestDisposable = getLongpollServerInfo()
.subscribe(onNext: {[weak self] info in
guard let sSelf = self else { return }
sSelf.currentRequestInfo.value = info
})
currentRequestDisposable?.addDisposableTo(disposeBag)
}
private func parseUpdates(json: Any) throws -> Observable<LongPollingUpdates> {
let response = try Mapper<LongPollingUpdates>().map(JSONObject: json)
return .just(response)
}
private func reciveEvents(updates:LongPollingUpdates) throws -> Observable<TimeInterval> {
if let errors = updates.failed {
throw parseErrors(errors: errors)
}
if let events = updates.updates {
parseUpdates(updates: events)
}
return Observable.just(updates.timeStamp!)
}
private func parseUpdates(updates: [[Any]]) {
updates.forEach { (array) in
let firstElementInUpdate = array.first
if let update = firstElementInUpdate as? Int {
switch update {
case 1: break
case 2: break
case 3: break
case 4: messageReciver.onNext(NewMessage(array: array))
default: break
}
}
}
}
private func parseErrors(errors: [String: Any]) -> LongPollError {
if let error = errors["failed"] as? Int {
switch error {
case 1:
guard let ts = errors["ts"] as? TimeInterval else { return .unkownError }
return .olderHistory(ts: ts)
case 2: return .needNewkey
case 3: return .needCaseAndTs
case 4: return .unkownVersion
default:
return .unkownError
}
}
return .unkownError
}
private func getEventsFromLongpollServer(serverInfo: LongpollingServerInfo, with ts: TimeInterval) -> Observable<Any> {
let url = buildLongPollingServerRoute(from: serverInfo, with: ts)
let request = buldLongPollRequst(route: url)
let requestConvert = try? URLEncoding.default.encode(request!, with: nil)
return transport.makeRequest(request: requestConvert!)
}
private func getEventsFromLongpollServer(serverInfo: LongpollingServerInfo) -> Observable<Any> {
let url = buildLongPollingServerRoute(from: serverInfo)
let request = buldLongPollRequst(route: url)
let requestConvert = try? URLEncoding.default.encode(request!, with: nil)
return transport.makeRequest(request: requestConvert!)
}
private func getLongpollServerInfo() -> Observable<LongpollingServerInfo> {
let request = MessageRouter.getLongpollServer(useSsl: false, needPts: false)
return transport.makeModel(request: request)
}
}
So assuming you have a function like:
func getData() -> Observable<Data>
And you want to long poll it at a specific period, you can do something like this:
Observable<Int>.interval(period, scheduler: MainScheduler.instance)
.map { _ in return }
.flatMap(getData)
.subscribe( /* ... handle data ... */)
.disposed(by: disposeBag)
You can use other schedulers than MainScheduler if that is more appropriate.
Now if you want also handle Errors that getData might emit and you don't want that to necessarily unsubscribe the long polling, then you can do this:
func handleError(error: Error) -> Observable<Data> {
return Observable.empty()
}
Observable<Int>.interval(period, scheduler: MainScheduler.instance)
.map { _ in return }
.flatMap { return getData.catchError(handleError) }
.subscribe( /* ... handle data ... */)
.disposed(by: disposeBag)
You can also analyze the error in handleError and decide if you want to continue by emitting an empty Observable or cancel the long polling by emitting another error.