How to trigger an observable when another observable fires in RxSwift? - swift

I have the following observable:
observable1
.withLatestFrom(observable2) { (obs1Value, obj2Value) -> [SomeDataType] in
return obj1Value.someFunction(for: obj2Value)
}
.bind(to: someSubject)
.disposed(by: disposeBag)
And it should be updated when the following PublishRelay fires:
let publishRelay = PublishRelay<AnotherDataType>()
observable1 and observable2 have 2 different types
I've tried the following:
Observable.combineLatest(observable1, observable2, publishRelay.asObservable())
.map { obs1Value, obj2Value, _ in
return obj1Value.someFunction(for: obj2Value)
}
.bind(to: someSubject)
.disposed(by: disposeBag)
But have faced the problem that observable1 & observable2 are not being binded until publishRelay fires (it basically waits for all 3 values to arrive)
I need to the observables to go through even without PublishRelay but get an extra update when the PublishRelay fires (I'm not using any data from PublishRelay inside of the observables subscription)
What's the best way to achieve the above?
Thanks!

The key here is to use startWith in order to "pre-fire" the offending observable so it has a latest value to work with. Since you don't care about the relay's value, you can map to unit and startWith unit.
It would look something like this:
Observable.combineLatest(observable1, publishRelay.asObservable().map { _ in () }.startWith(()))
.withLatestFrom(observable2) { $0.0.someFunction(for: $1) }
.bind(to: someSubject)
.disposed(by: disposeBag)

Related

RxSwift BehaviorRelay cancel previous calls, only use the most recent

I have a BehaviorRelay setup to store an array of addresses, and then I observe that BehaviorRelay so that I can create an MKAnnotation array and then display that on the map.
let addresses = BehaviorRelay<[Address]>(value: [])
I make network requests when the user moves the map to a new area.
If the user moves the map very quickly, I can end up with several network requests
I only want the latest response.
This is where my problem begins.
addresses.asObservable().subscribe(onNext: { [unowned self] (value) in
self.fetchAllAnnotationsAndAddToMap()
}).disposed(by: disposebag)
fetchAllAnnotationsAndAddToMapgets called every time addresses is set.
fetchAllAnnotationsAndAddToMap can take a long time to complete. Requests to run fetchAllAnnotationsAndAddToMapget stacked up and all of them run to completion.
What I want to happen is once addresses is set again, I want for all former calls to be discarded and only use the latest.
I've heard this is what flatMapLatest is for.
However, flatMapLatest requires I return an observable and I'm confused by that.
How do I cancel the methods called after the BehaviorRelay is updated and only use the most recent?
First you need to break up your fetchAllAnnotationsAndAddToMap() function into two functions. Then you can:
addresses.asObservable()
// you might want to put a `debounce` call in here.
.flatMapLatest { addresses in
return fetchAllAnnotations(from: addresses)
}
.subscribe(onNext: { [weak self] annotations in
self?.addToMap(annotations)
}
.disposed(by: disposeBag)
Probably using a flatMapLatest is a designated solution to this problem.However, I would like to propose another solution :
Simply re-initialize your BehaviorRelay again in your subscribe part of your code, and filter empty ones, Like this :
addresses.asObservable().filter({$0.count >0}).subscribe(onNext: { [unowned self] (value) in
self.fetchAllAnnotationsAndAddToMap()
self.addresses = BehaviorRelay<[Address]>(value: [])
}).disposed(by: disposebag)

Subscribe to view controller property without nested subscribe loop

How can I make this subscribe loop not be nested? I can't seem to figure out how you would go about doing this because I push the view controller in the main subscribe loop, and not just set a value.
button.rx.tap.subscribe(onNext: { _ in
let viewController = MyViewController()
self.navigationController.pushViewController(viewController)
viewController.myPublishRelay.asObservable().subscribe(onNext: { value in
// do something with value
})
})
You desire two different side effects, so it makes sense to have two subscription. To prevent from nesting, you could do something in the lines of this:
let viewControllerToPresent = button.rx.tap.map { _ in
return MyViewController()
}.share()
viewControllerToPresent.subscribe(onNext: { [unowned self] viewController in
self.view.pushViewController(viewController)
}.disposed(by: self.disposeBag)
viewControllerToPresent.flatMapLatest { viewController in
viewController.myPublishRelay.asObservable()
}.subscribe(onNext: { value in
// do something with value
}.disposed(by: self.disposeBag)
The call to share is important here, otherwise the mapping after rx.tap would occur twice, making us subscribe to the publish relay of a view controller that is not the one we presented.
You can use .sample() or .combineLatest(), depending on how does your publishRelay update.
For example, Observable.combineLatest(myPublishRelay, button.rx.tap) { $0 }.subscribe(onNext: { value ...
See http://rxmarbles.com for reference on operators.
Whenever I see a nested subscribe I think of flatMap. Something like this should work:
button.rx.tap
.flatMap { _ in
let viewController = MyViewController()
self.navigationController.pushViewController(viewController)
return viewController.myPublishRelay.asObservable()
}
.subscribe(onNext: { value in
// do something with value
})

RxSwift: how to build up the observable function call chain other than use callback?

I'm trying to solve an async sequential problem, for example:
There are one 'OriginalData' in myclass, and I want to do some sequential operations to it: operationA, operationB and operationC:
operationA accepts OriginalData and return outputA, after it finishes, then operationB should take the outputA as input and return outputB, and move to operationC..
What I've done was using the callbacks:
// pseudocode
class Myclass {
func operationA(inputA, callback: operationB)
func operationB(inputB, callback: operationC)
..
}
As a result, if using callbacks, it will result in a callback hell and lots trouble. I turned into RxSwift, but not sure how to use RxSwift to solve it.
(P.S I've already read the RxSwift's official document, but still cannot make my idea clear. Best Appreciated for your helps!)
I think you can solve this problem by using PublishSubject as follows:
let operationA = PublishSubject<String>()
let operationB = PublishSubject<String>()
let operationC = PublishSubject<String>()
let disposeBag = DisposeBag()
operationA.asObserver()
.subscribe(onNext:{element in
operationB.onNext(element)
})
.disposed(by: disposeBag)
operationB.asObserver()
.subscribe(onNext:{element in
operationC.onNext(element)
})
.disposed(by: disposeBag)
operationC.asObserver()
.subscribe(onNext:{element in
print(element)
})
.disposed(by: disposeBag)
operationA.onNext("A")

What is the difference between .subscribe and .drive

I am quite new in Reactive Programming, Here is what I'm trying
.drive
searchController.rx.text
.asDriver()
.drive(onNext: { (element) in
print(element)
}).disposed(by: disposeBag)
.subscribe
searchController.rx.text
.asObservable()
.subscribe(onNext: { (element) in
print(element)
}).disposed(by: disposeBag)
both blocks are working exactly the same, What is the purpose of using .drive over .subscribe? In which scenario we should use .drive over .subscribe ?
Any help will be appreciated
Driver is a bit different from Observable. From documentation:
Trait that represents observable sequence with following properties:
it never fails
it delivers events on MainScheduler.instance
share(replay: 1, scope: .whileConnected) sharing strategy
I assume that searchController.rx.text never fails and share isn't required in this situation.
So we have only one point that makes them different in your situation:
it delivers events on MainScheduler.instance
And you can check it yourself. Before subscribe insert this and your events won't be delivered on main thread:
.observeOn(ConcurrentDispatchQueueScheduler(qos: .background))
That is how I checked it in my code:
something
.asObservable()
.observeOn(ConcurrentDispatchQueueScheduler(qos: .background))
.subscribe(onNext: { _ in
print("observable is on main thread: ", Thread.isMainThread)
})
something
.asDriver()
.drive(onNext: { _ in
print("driver is on main thread: ", Thread.isMainThread)
})
Logs:
driver is on main thread: true
observable is on main thread: false
In which scenario we should use .drive:
When working with UI. Why? From documentation:
Important
Use UIKit classes only from your app’s main thread or main
dispatch queue, unless otherwise indicated. This restriction
particularly applies to classes derived from UIResponder or that
involve manipulating your app’s user interface in any way.

RxSwift: disposed(by:) weirdness

So the code below compiles with the errror
var doneSubscription: Disposable = item.doneSubjectObservable
.debug("doneSubscriptions")
.subscribe(
onNext: {
done in self.validateDone(done: done, item: item)
}).disposed(by: disposeBag)
Value of type '()' does not conform to specified type 'Disposable'
on the line .disposed(by: disposeBag)
But I can do this without error:
var doneSubscription: Disposable = item.doneSubjectObservable
.debug("doneSubscriptions")
.subscribe(
onNext: {
done in self.validateDone(done: done, item: item)
})
doneSubscription.disposed(by: disposeBag)
All I've done is moved .disposed(by: disposeBag) out of the subscription chain.
Am I missing something, aren't these two approaches equivalent?
No, they are not equivalent.
In the first case, you are storing the return value of this whole expression into doneSubscription, a variable of type Disposable:
item.doneSubjectObservable
.debug("doneSubscriptions")
.subscribe(
onNext: {
done in self.validateDone(done: done, item: item)
}).disposed(by: disposeBag)
Since disposed(by:) does not return anything, this gives you an error.
In the second case however, you actually assigned a Disposable to the variable doneSubscription - the return value of subscribe.
To fix your first case, simply remove the variable declaration.