Schedulers in Combine: Part 2: RunLoop Scheduler
iOS Combine Estimated reading time: 7 minutesLet’s continue to review available Schedulers
in Combine
. This is the second article in the series and here we will review RunLoop
as a Scheduler
.
Just to quick recap from prev. article - Scheduler
it’s a protocol the can be used to define how a certain amount of work can be done.
Also, it’s a good idea to remind ourselves about RunLoop
, thus we will use it next. To do so, u can check my article about RunLoop
. Simply speaking RunLoop
- is a mechanism that allows us to manage inputSources
and timers using Thread
.
Related articles:
- Schedulers in Combine. Part 1: ImmediateScheduler
- Schedulers in Combine: Part 2. RunLoop Scheduler
- Schedulers in Combine. Part 3: DispatchQueue Scheduler
- Schedulers in Combine. Part 4: OperationQueue Scheduler
RunLoop Scheduler
RunLoop
scheduler associated with concrete Thread
, thus Thread
works with RunLoop
and may create it if needed for us.
The main functions of any Scheduler
are to define how (using some options) and when (now or in future) code will be executed.
RunLoop+Scheduler
is openSource and available here for inspection.
So, let’s try to use RunLoop
as Scheduler
and check HOW code can be executed. The simplest case - we just setup scheduler and run in using RunLoop as a Scheduler:
So, the result is as we expected - everything up and running.
Let’s try to play a bit with process (make it more likely to real one) and change the code, so we do same but on different Thread:
What we will receive? Let’s run and check this:
Well - we just see that process started and that it. Why? Remember I mentioned above that RunLoop
created by Thread
if needed, so maybe it does not exist? Let’s check this by calling RunLoop.current
within the selected queue.
Looks like RunLoop
exists and created for us… What’s wrong then? Maybe we should explicitly call run
?
But before we do so, how do we know that run executed successfully? Let’s use API that let us know about this - run(mode:before) -> Bool
- return value is true if the run loop ran and processed an input source or if the specified timeout value was reached; otherwise, false if the run loop could not be started..
Add this right before the publisher:
and nothing… When we checked the result, we see false… So RunLoop
is simple don’t run and that the reason why we didn’t see any output from the publisher that uses RunLoop
as a Scheduler
.
Why? Because if no input sources or timers are attached to the run loop, this method exits immediately and returns false; otherwise, it returns after either the first input source is processed or limitDate is reached. This means that calling this func is not enough - we should call it from the right place. So let’s move it to the very end - right after we configure publisher and event receiving.
Finally, all works as expected. Now we “informed” RunLoop
from selected Thread that it has something to do, and it does. We can even change .receive(on: RunLoop.main)
to .receive(on: RunLoop.current)
, and instead
we will get somethig like (number of the Thread
may be different on your side):
So - everything works as we expect. But there is one more point that I would like to clarify - RunLoop mode
.
In the call above we used default
mode - mode usage is a bit tricky and we used as it visible from the name - the default
one.
Try to change it to common
. Yep, nothing works - it’s because RunLoop
can be only in ONE mode, and by default, it’s in default
mode.
check more about
RunLoop
andmodes
in my other article
We already discuss a bit the RunLoop
mode and how it works. But here is one more point that needs to be mentioned - UIKit
and AppKit
run the RunLoop
in the default
mode when idle. But, in particular, when tracking a user interaction (like a touch or a mouse button press), they run the RunLoop
in a different, non-default mode. So a Combine
pipeline that uses receive(on: RunLoop.main)
will not deliver signals while the user is touching or dragging.
Thanks Rob Mayoff for his comments on Swift forum and well known StackOverflow
Also, it’s good to know how RunLoop Scheduler executes the code, and according to the source:
as u can see here perfrom
is called an action that will be executed in the next iteration or RunLoop
loop, so almost immediately.
Now it’s time to check another art of responsibilities required by Scheduler and related to WHEN code should be executed.
As we discussed previously Scheduler may configure execution work either now either in the future. And For this purpose used SchedulerTimeType
.
Not all Scheduler can execute work in future - for example,
ImmediateScheduler
can’t
If we check source code or API
for RunLoop
scheduler, we can find that SchedulerTimeTipe
for RunLoop
works with Date
:
This makes it possible to easily configure any future time. But before testing it we need to check one more type that represents SchedulerOptions - RunLoop.SchedulerOptions
:
Yep, there is no option available. And if we check source code, this param is never used also, so just ignored:
Now we can test how this work:
u can replace
RunLoop.current
toRunLoop.main
and skip run call, or just ommit usingqueue.async
pitfalls
- Make sure that
RunLoop
you are using is running and in expected mode. receive(on: RunLoop.main)
will not deliver signals while the user is touching or dragging.- there is a possible minimal delay while
perform
executed (usually not important) RunLoop
is not Thread-safe - so be careful when using it.- avoid
RunLoop.current
if u not sure in usage and instead useRunLoop.main
orDispatchQueue
usage example
Timers
requireRunLoop
to run on and specific mode, so without RunLoop Timer publisher can’t be created:
- Gesture’s can’t be processed without
RunLoop
- Good sample of usage may be backgroundLogger - when u need to log everything u can use your own
Thread
andRunLoop
for this. Then logging will be done efficiently. Sample - downloading a lot of images in some Feed - here u can also use RunLoop in default mode - check AliExpress app: when u scroll the feed, images are not loading, but when u stop, they are.
- check AsyncDisplayLink - another good sample
In the next part, I will cover DispatchQueue Scheduler
.
Related articles:
- Schedulers in Combine. Part 1: ImmediateScheduler
- Schedulers in Combine: Part 2. RunLoop Scheduler
- Schedulers in Combine. Part 3: DispatchQueue Scheduler
- Schedulers in Combine. Part 4: OperationQueue Scheduler
Share on: