What is the order of operations on iOS?
I'm thinking sepcifically about timing of
setNeedsLayout
and layoutSubviews
setNeedsDisplay
and drawRect
[NSTimer scheduledTimerWithTimeInterval:0.000001 tar(...)]
dispatch_async(dispatch_get_main_queue(), ^{ /* code */}
As an example of an answer I would like to receive it could be in this format:
dispatch_async on main Happens before the next runcycle
drawRect Happens at the end of the runcycle
(Parts of this are copied from my answer to a similar question.)
It turns out that the run loop is complicated, and a simple question like “Does drawRect:
happen at the end of the runcycle?” doesn't have a simple answer.
CFRunLoop
is part of the open-source CoreFoundation package, so we can take a look at exactly what it entails. The run loop looks roughly like this:
while (true) {
Call kCFRunLoopBeforeTimers observer callbacks;
Call kCFRunLoopBeforeSources observer callbacks;
Perform blocks queued by CFRunLoopPerformBlock;
Call the callback of each version 0 CFRunLoopSource that has been signaled;
// Touch events are a version 0 source in iOS 8.0.
// CFSocket is a version 0 source.
if (any version 0 source callbacks were called) {
Perform blocks newly queued by CFRunLoopPerformBlock;
}
if (I didn't drain the main queue on the last iteration
AND the main queue has any blocks waiting)
{
remove all blocks from the main queue
execute all the blocks just removed from the main queue
} else {
Call kCFRunLoopBeforeWaiting observer callbacks;
// Core Animation uses a BeforeWaiting observer to perform layout and drawing.
Wait for a CFRunLoopSource to be signalled
OR for a timer to fire
OR for a block to be added to the main queue;
Call kCFRunLoopAfterWaiting observer callbacks;
if (the event was a timer) {
call CFRunLoopTimer callbacks for timers that should have fired by now
} else if (event was a block arriving on the main queue) {
remove all blocks from the main queue
execute all the blocks just removed from the main queue
} else {
look up the version 1 CFRunLoopSource for the event
if (I found a version 1 source) {
call the source's callback
}
// Interface orientation changes are a version 1 source in iOS 8.0.
}
}
Perform blocks queued by CFRunLoopPerformBlock;
}
Core Animation registers a kCFRunLoopBeforeWaiting
observer with an order of 2000000 (although that is not documented; you can figure it out by printing [NSRunLoop mainRunLoop].description
). This observer commits the current CATransaction
, which (if necessary) performs layout (updateConstraints
and layoutSubviews
) and then drawing (drawRect:
).
Note that the run loop can evaluate the true
in while(true)
twice before executing BeforeWaiting observers. If it dispatches timers or a version 1 source, and that puts block on the main queue, the run loop will go around twice before calling the BeforeWaiting observers (and it will dispatch version 0 sources both times).
The system uses a mixture of version 0 sources and version 1 sources. In my testing, touch events are delivered using a version 0 source. (You can tell by putting a breakpoint in a touch handler; the stack trace contains __CFRunLoopDoSources0
.) Events like entering/leaving foreground are dispatched through CFRunLoopPerformBlock
, so I don't know what kind of source really provides them. Interface orientation changes are delivered through a version 1 source. CFSocket
is documented to be a version 0 source. (It's likely that NSURLSession
and NSURLConnection
use CFSocket
internally.)
Note that the run loop is structured so only one of these branches happens on each iteration:
dispatch_get_main_queue()
run, or
After that, any number of version 0 sources can call their callbacks.
So:
Also remember that you can request immediate layout at any time using layoutIfNeeded
.
One task after the other is added to the runloop from various sources; the runloop will execute the oldest task on the runloop and not start another task until the call for that task returns.
setNeedsLayout
and setNeedsDisplay
if they need an updatelayoutSubviews
(called indirectly by layoutSublayers
)drawRect
and drawInContext:
dispatch_async
call is performed0.000001
seconds delay could be executed before or after the dispatch_async
. Difficult to say.1 and 2 are actually mixed because it's mostly the user interaction causing changes in the UI by calling setNeedsLayout
and setNeedsDisplay
somewhere.
The order of 1, 2, 3 and 4 is well-defined. 5 should also always happen afterwards. NSTimer
depends on various circumstances - you should not rely that it's called before or after the dispatch_async
call but it most likely will be executed after the painting is done.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With