What is the difference between .subscribe and .drive - reactive-programming

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.

Related

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

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)

Combine`s subscribe(on:options:) operator

I have a question about the subscribe(on:options:) operator. I would appreciate if anyone can help me to figure it out.
So what we have from the documentation:
Specifies the scheduler on which to perform subscribe, cancel, and request operations.
In contrast with receive(on:options:), which affects downstream messages, subscribe(on:options:) changes the execution context of upstream messages.
Also, what I got from different articles is that unless we explicitly specify the Scheduler to receive our downstream messages on (using receive(on:options:)), messages will be send on the Scheduler used for receiving a subscription.
This information is not aligned with what I am actually getting during the execution.
I have the next code:
Just("Some text")
.map { _ in
print("Map: \(Thread.isMainThread)")
}
.subscribe(on: DispatchQueue.global())
.sink { _ in
print("Sink: \(Thread.isMainThread)")
}
.store(in: &subscriptions)
I would expect next output:
Map: false
Sink: false
But instead I am getting:
Map: true
Sink: false
The same thing happens when I use Sequence publisher.
If I swap the position of map operator and subscribe operator, I receive what I want:
Just("Some text")
.subscribe(on: DispatchQueue.global())
.map { _ in
print("Map: \(Thread.isMainThread)")
}
.sink { _ in
print("Sink: \(Thread.isMainThread)")
}
.store(in: &subscriptions)
Output:
Map: false
Sink: false
Interesting fact is that when I use the same order of operators from my first listing with my custom publisher, I receive the behaviour I want:
struct TestJust<Output>: Publisher {
typealias Failure = Never
private let value: Output
init(_ output: Output) {
self.value = output
}
func receive<S>(subscriber: S) where S : Subscriber, Failure == S.Failure, Output == S.Input {
subscriber.receive(subscription: Subscriptions.empty)
_ = subscriber.receive(value)
subscriber.receive(completion: .finished)
}
}
TestJust("Some text")
.map { _ in
print("Map: \(Thread.isMainThread)")
}
.subscribe(on: DispatchQueue.global())
.sink { _ in
print("Sink: \(Thread.isMainThread)")
}
.store(in: &subscriptions)
Output:
Map: false
Sink: false
So I think there is either my total misunderstanding of all these mechanisms, or some publishers intentionally choose the thread to publish values (Just, Sequence -> Main, URLSession.DataTaskPublisher -> Some of Background), which does not make sense for me, cause in this case why would we need this subscribe(on:options:) for.
Could you please help me to understand what am I missing? Thank you in advance.
The first thing to understand is that messages flow both up a pipeline and down a pipeline. Messages that flow up a pipeline ("upstream") are:
The actual performance of the subscription (receive subscription)
Requests from a subscriber to the upstream publisher asking for a new value
Cancel messages (these percolate upwards from the final subscriber)
Messages that flow down a pipeline ("downstream") are:
Values
Completions, consisting of either a failure (error) or completion-in-good-order (reporting that the publisher emitted its last value)
Okay, well, as the documentation clearly states, subscribe(on:) is about the former: messages that flow upstream. But you are not actually tracking any of those messages in your tests, so none of your results reflect any information about them! Insert an appropriate handleEvents operator above the subscription point to see stuff flow upwards up the pipeline (e.g. implement its receiveRequest: parameter):
Just("Some text")
.handleEvents(receiveRequest: {
_ in print("Handle1: \(Thread.isMainThread)")
})
.map // etc.
Meanwhile, you should make no assumptions about the thread on which messages will flow downstream (i.e. values and completions). You say:
Also, what I got from different articles is that unless we explicitly specify the Scheduler to receive our downstream messages on (using receive(on:options:)), messages will be send on the Scheduler used for receiving a subscription.
But that seems like a bogus assumption. And nothing about your code determines the downstream-sending thread in a clear way. As you rightly say, you can take control of this with receive(on:), but if you don't, I would say you must assume nothing about the matter. Some publishers certainly do produce a value on a background thread, such as the data task publisher, which makes perfect sense (the same thing happens with a data task completion handler). Others don't.
What you can assume is that operators other than receive(on:) will not generally alter the value-passing thread. But whether and how an operator will use the subscription thread to determine the receive thread, that is something you should assume nothing about. To take control of the receive thread, take control of it! Call receive(on:) or assume nothing.
Just to give an example, if you change your opening to
Just("Some text")
.receive(on: DispatchQueue.main)
then both your map and your sink will report that they are receiving values on the main thread. Why? Because you took control of the receive thread. This works regardless of what you may say in any subscribe(on:) commands. They are different matters entirely.
Maybe if you call subscribe(on:) but you don't call receive(on:), some things about the downstream-sending thread are determined by the subscribe(on:) thread, but I sure wouldn't rely on there being any hard and fast rules about it; there's nothing saying that in the documentation! Instead, don't do that. If you implement subscribe(on:), implement receive(on:) too so that you are in charge of what happens.

RxSwift: How to respond to a series of notifications?

Suppose I have two notifications coming one after another. I need to wait for work to complete from 1st notification and only then fire the work from 2nd notification. For now, I tried to schedule sequences to a serial scheduler, but it doesn't work as expected, it seems that I'm missing something.
NotificationCenter.default.rx.notification(.notification1)
.observeOn(MainScheduler.instance)
.subscribe(onNext: { [weak self] in
self?.doSomeAsynchWork() //Fires another subscription, kind of ugly
})
.disposed(by: disposeBag)
NotificationCenter.default.rx.notification(.notification2)
.observeOn(MainScheduler.instance)
.subscribe(onNext: { [weak self] in
self?.doSomeWork() //This should only be executed after doSomeAsynchWork() is done
})
.disposed(by: disposeBag)
I was expecting work to be done in a serial manner, but that's not the case, my guess is that doSomeAsynchWork() is, well, asynchronous and doSomeWork() fires right after. But can I somehow wait for asynchronous work to complete? Any help is appreciated
UPD: notification2 may or may not arrive, so they are kind of independent of each other. Also, notification1 may or may not arrive, it's just different use cases in the app. But when both notification1 and notification2 are present, I need to wait for doSomeAsynchWork() to finish
The flow is as follows:
User taps to block some element in the list, which is only allowed for a signed-in user
User gets redirected to a sign-in screen
User sings in and then, notification1 fires
We continue to block that element now that we're signed in
Notification2 fires
The problem is when notification1 fired, we need to reload the screen, so that logic comes to doSomeAsynchWork(). On top of that, we're getting the "delete element" notification and we're trying to locate the element which is not there yet, so we're kind of stuck with an inconsistent state, where the element's blocked, but still present on a screen
The difficulty is that we can sign-in without blocking element and we can block element without the need of signing-in in (because we are already signed-in for example)
Based on the flow you described in your update, I would expect to see something like this:
func example(tapElement: Observable<ID>, isLoggedIn: Observable<Bool>, presentLogin: Observable<Void>) {
tapElement
.withLatestFrom(isLoggedIn) { (id: $0, isLoggedIn: $1) }
.flatMapFirst { id, isLoggedIn in
isLoggedIn ? Observable.just(id) : presentLogin.map { id }
}
.subscribe(onNext: { id in
blockElement(id: id)
})
}
I don't see any reason to have all the notifications in the first place.
Old Answer
I would have doSomeAsynchWork() return an Observable<Void> which emits an event with the async work is complete. Then I could:
NotificationCenter.default.rx.notification(.notification1)
.flatMap { doSomeAsynchWork() }
.subscribe(onNext: { doSomeWork() }
Another option would be to have doSomeAsynchWork() return a Completable, then you would do something like:
NotificationCenter.default.rx.notification(.notification1)
.flatMap { doSomeAsynchWork() }
.subscribe(onCompleted: { doSomeWork() }

How to drop new elements if an observer is busy?

I have an observable which regularly emits elements. On those elements, I perform one fast and one slow operation. What I want is to drop new elements for slow observer while it is busy. Is there any way to achieve this with Rx instead of keeping a flag in slow operation?
I am very new at Reactive extensions, please correct me if anything is wrong with my assumptions.
let tick = Observable<Int>.interval(.seconds(1),
scheduler: SerialDispatchQueueScheduler(qos: .background)).share()
tick.subscribe {
print("fast observer \($0)")
}.disposed(by: disposeBag)
// observing in another queue so that it does not block the source
tick.observeOn(SerialDispatchQueueScheduler(qos: .background))
.subscribe {
print("slow observer \($0)")
sleep(3) // cpu-intensive task
}.disposed(by: disposeBag)
For this, flatMap is your friend. Whenever you want to drop events (either the current one when a new one comes in, or subsequent ones while working on the current one) use flatMap. More information can be found in my article: RxSwift’s Many Faces of FlatMap
Here you go:
let tick = Observable<Int>.interval(.seconds(1), scheduler: MainScheduler.instance).share()
func cpuLongRunningTask(_ input: Int) -> Observable<Int> {
return Observable.create { observer in
print("start task")
sleep(3)
print("finish task")
observer.onNext(input)
observer.onCompleted()
return Disposables.create { /* cancel the task if possible */ }
}
}
tick
.subscribe {
print("fast \($0)")
}
.disposed(by: disposeBag)
tick
.flatMapFirst {
// subscribing in another scheduler so that it does not block the source
cpuLongRunningTask($0)
.subscribeOn(SerialDispatchQueueScheduler(qos: .background))
}
.observeOn(MainScheduler.instance) // make sure the print happens on the main thread
.subscribe {
print("slow \($0)")
}
.disposed(by: disposeBag)
Sample output as follows:
fast next(0)
start task
fast next(1)
fast next(2)
fast next(3)
finish task
slow next(0)
fast next(4)
start task
fast next(5)
fast next(6)
fast next(7)
finish task
slow next(4) <-- slow ignored the 1, 2, and 3 values.
I'm afraid there is not a straightforward solution. The issue you describe is related to backpressure and unfortunately, RxSwift does not provide support for it (Apple Combine does). Usually, you will have to handle this situation manually by using one of the filtering operators: debounce, throttle or filter.
By using debounce or throttle you would need to know the exact duration of the operation which probably is not always the case.
By using filter, as you said, you could check for a flag you set before starting the long-running operation.

Realm notifications registration while in write transaction

I understand that you can not register a Realm .observe block on an object or collection if the Realm is in a write transaction.
This is easier to manage if everything is happening on the main thread however I run into this exception often because I prefer to hand my JSON parsing off to a background thread. This works great because I don't have to bog down the main thread and with Realm's beautiful notification system I can get notified of all modifications if I have already registered to listen for those changes.
Right now, if I am about to add an observation block I check to make sure my Realm is not in a write transaction like this:
guard let realm = try? Realm(), !realm.isInWriteTransaction else {
return
}
self.myToken = myRealmObject.observe({ [weak self] (change) in
//Do what ever
}
This successfully guards against this exception. However I never get a chance to re - register this token unless I get a little creative.
Does the Realm team have any code examples/ suggestions on a better pattern to avoid this exception? Any tricks I'm missing to successfully register the token?
In addition to the standard function, I do use an extension for Results to avoid this in general. This issue popped up, when our data load grew bigger and bigger.
While we do now rewrite our observe functions logic, this extension is an interims solution to avoid the crashes at a first place.
Idea is simple: when currently in a write transaction, try it again.
import Foundation
import RealmSwift
extension Results {
public func safeObserve(on queue: DispatchQueue? = nil,
_ block: #escaping (RealmSwift.RealmCollectionChange<RealmSwift.Results<Element>>) -> Void)
-> RealmSwift.NotificationToken {
// If in Write transaction, call it again
if self.realm?.isInWriteTransaction ?? false {
DispatchQueue.global().sync {
Thread.sleep(forTimeInterval: 0.1) // Better to have some delay than a crash, hm?
}
return safeObserve(on: queue, block)
}
// Aight, we can proceed to call Realms Observe function
else {
return self.observe(on: queue, block)
}
}
}
Then call it like
realmResult.safeObserve({ [weak self] (_: RealmCollectionChange<Results<AbaPOI>>) in
// Do anything
})