I'm not sure how I should ask my question.
Often, when you develop UIKit apps, you need both to make sure some code runs on the main queue, and to avoid a queue hop (DispatchQueue.main.async { ... }
) if the caller is already running on the main queue.
Really?
Yes! People do check for Thread.isMain
, or for the main DispatchQueue (with specifics), in order to preserve a synchronous behavior when possible!
Come on, this thing is so subtle, and so important, that RxSwift has three ways to specify how you want your code to be dispatched on the main queue:
-
MainScheduler.asyncInstance
: always after a queue hop -
MainScheduler.instance
: always after a queue hop, unless the caller is already on the main queue, and the scheduler is not called in a reentrant way. - ConcurrentMainScheduler.instance: : always after a queue hop, unless the caller is already on the main queue.
Talk about a crucial feature!
Guarantees of synchronous calls are critical in order to avoid unwanted visual glitches.
Let's give an example. Say you want to push a view controller that displays information about a book. It is crucial that the book is available before the push animation starts, or you may see a flash of an empty screen right in the middle of the push animation. This flash is the sign of an unwanted DispatchQueue.main.async { ... }
.
You'll ask: "but why don't you just load your book before pushing your screen?"
I'll answer: this muddles responsibilities. Sometimes you really want to encapsulate the book screen and its data source / view model / store / whatever as a unit that is fully responsible for loading and displaying the needed data. So you do not want the previous screen, the one from which you navigate to the book screen, to be responsible for getting a book value before pushing the book screen. Instead, you want the book screen to be autonomous, and so you need to make sure it is able to grab the book synchronously, in order to avoid the visual glitches I described above.
The consequence is that it would be really not good at all if undesired dispatch queue hops could happen when two @MainActor
functions / classes / closures call one each other, or when undecorated code that happens to run on the main queue calls a @MainActor
function / class / closure.
To me, this is the difference between a tool that is gorgeous and does what I need, and a tool that stabs me in the back after I've spent hours and days refactoring my code for the new shiny fad. There is nothing worse than discovering that the tool can not do what I need (it's not that it is difficult, it is that it is impossible, because the tool does not understand what people need).
Please remember that UI programming sure needs guarantees to run on the main queue, but it also needs a lot of synchronous code.
Is this topic addressed in the proposal? Am I alone feeling somewhat nervous?