I have red the documentation about Driver and I realized that it is the best option to connect my observables (now changed to drivers) to my UI elements because they warranty me that the connection is being done in the Main thread (or at least that is what I understood ;P)
I tried it just to check and this is what I got:
// self.avatar is and UIImageView
let variable: Variable<UIImage?> = Variable(UIImage())
let driver = variable.asDriver()
// Execute in background thread
Async.background {
/* Breaks with:
fatal error: Executing on backgound thread.Please use `MainScheduler.instance.schedule` to schedule work on main thread.: file / Users / andres / Git / Worktoday / Worktoday / Pods / RxSwift / RxSwift / Rx.swift, line 23
*/
driver.drive(self.avatar.rx_image).addDisposableTo(self.disposeBag)
/*Does not break, runs properly*/
variable.asObservable().observeOn(MainScheduler.instance).bindTo(self.avatar.rx_image).addDisposableTo(self.disposeBag)
}
I was expecting the drive function to change to the main thread and run without breaking.
Am I missing something?
Thanks!
Ok, maybe I went too fast, just saw this https://github.com/ReactiveX/RxSwift/issues/743
So, the only way to warranty that a binding is done in the MainThread (independently of the thread your are coming from) is explicitly using observeOn(MainScheduler.instance) (that is not available in Driver)? In that case and for this scenario, I do not see the point of using drivers, because anyway I would need to do:
driver.asObservable().observeOn(MainScheduler.instance).bindTo(self.avatar.rx_image).addDisposableTo(self.disposeBag)
Hi @acecilia ,
You need to call drive* from main thread. Everything documented is true. Driver does indeed have observeOn(MainScheduler.instance) internally to ensure elements are received on main thread.
It is easy to verify that source code for ObservableType.asDriver* methods contains observeOn(MainScheduler.instance) to make sure all event are emitted on main scheduler.
The reason why you need to also call drive method from main thread is because it also has shareReplay operator that is used as a sharing strategy that replays latest element immediately upon subscription on the same thread which is calling drive* method.
This is the scenario:
let xs: Observable<[DatabaseEntity]> = ....
let xsDriver: Driver<[DataBaseEntity]> = xs.asDriver(onErrorJustReturn: [])
// this is on main thread
xsDriver.driveNext { entities in
print("E1 \(entities)")
}
// prints E1 [....
// so now somebody does this
Async.background {
// because `Driver`
xsDriver.driveNext { entities in
print("E2 \(entities)")
}
// first print would be called on background thread because
// it will be replayed immediately from `shareReplay` operator,
// and subsequent elements would be printed on main scheduler because
// they would pass through `observeOn(MainScheduler.instance)` operator.
// that assert is here to warn you that it is possible that first element emitting from
// `shareReplay` operator would happen on background thread
// if you always call `drive` method from main thread, then this can't happen since
// even that first element will be replayed on main thread
}
This is the rationale:
Async.background {
print(self.avatar) // retains UIView from background thread which isn't allowed
// as far as I'm aware of
}
observeOn operator would schedule binding on 2nd run loop pass, and thus potentially cause UI glitches.drive method from background thread since this is a concept intended to model UI data streams.Nice @kzaher, thank you so much for that great explanation, I appreciate it.
Most helpful comment
Hi @acecilia ,
You need to call
drive*from main thread. Everything documented is true.Driverdoes indeed haveobserveOn(MainScheduler.instance)internally to ensure elements are received on main thread.It is easy to verify that source code for
ObservableType.asDriver*methods containsobserveOn(MainScheduler.instance)to make sure all event are emitted on main scheduler.The reason why you need to also call
drivemethod from main thread is because it also hasshareReplayoperator that is used as a sharing strategy that replays latest element immediately upon subscription on the same thread which is callingdrive*method.This is the scenario:
This is the rationale:
observeOnoperator would schedule binding on 2nd run loop pass, and thus potentially cause UI glitches.drivemethod from background thread since this is a concept intended to model UI data streams.