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, orAfter 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
.