Reputation: 22962
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
Upvotes: 7
Views: 2217
Reputation: 385690
(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
.
Upvotes: 30
Reputation: 4676
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.
Upvotes: 1
Reputation: 52538
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.
Upvotes: 1