(Parts of this are copied from my answer to a similar question .)
It turns out that the startup loop is complicated, and the simple question is: "Does drawRect: happen at the end of the loop?" doesn't have a simple answer.
CFRunLoop is part of the open-source CoreFoundation package , so we can take a look at what it entails. The run loop looks something 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 signalled;
Core Animation registers the kCFRunLoopBeforeWaiting observer with the order of 2,000,000 (although this is not documented, you can understand this by typing [NSRunLoop mainRunLoop].description ). This observer captures the current CATransaction , which (if necessary) executes the layout ( updateConstraints and layoutSubviews ), and then draws ( drawRect: .
Note that the execution loop can evaluate true to while(true) twice before executing BeforeWaiting observers. If it sends timers or a source of version 1 and which puts the block in the main queue, the loop will cycle twice before calling BeforeWaiting observers (and it will send sources of version 0 both times).
The system uses a mixture of sources of version 0 and sources of version 1. In my testing, touch events are delivered using a source of version 0. (You can say by setting a breakpoint in the touch handler, the stack trace contains __CFRunLoopDoSources0 .) Events such as input / departure of the front plan are sent via CFRunLoopPerformBlock , so I donβt know which source really provides them. Interface orientation changes are delivered through source version 1. CFSocket documented as source version 0. (Probably, NSURLSession and NSURLConnection use CFSocket internally).
Note that the execution loop is structured, so only one of these branches occurs at each iteration:
- Ready to work with timers, or
- Lock on
dispatch_get_main_queue() run, or - A single source version 1 is sent to the callback.
After that, any number of sources of version 0 can call their callbacks.
So:
- A layout is always executed before drawing, if both are expected when the Core Animation observer starts. The CA observer starts after the start of timers, main queue blocks, or an external event callback.
- The main GCD queue has precedence in timers and sources of version 1, if , the run cycle did not deplete the main queue at the previous turn of the cycle.
- Timers take precedence over the main sources of the queue and version 1 if all three are ready.
- The main queue has precedence over versions 1, both must be ready.
Also remember that you can request an immediate layout at any time using layoutIfNeeded .