Runloop operation order on iOS

What is the order of operations with iOS?

I'm thinking about time

  • setNeedsLayout and layoutSubviews
  • setNeedsDisplay and drawRect
  • touch recognition
  • [NSTimer scheduledTimerWithTimeInterval:0.000001 tar(...)]
  • dispatch_async(dispatch_get_main_queue(), ^{ /* code */}

As an example of the answer I would like to receive, it could be in this format:

dispatch_async on the main Occurs before the next loop

drawRect Occurs at the end of a loop

+5
source share
3 answers

(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; // 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) { while (main queue has blocks) { perform the next block on 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) { while (main queue has blocks) { perform the next block on the main queue } } else { look up the version 1 CFRunLoopSource for the event if (I found a version 1 source) { call the source callback } // Interface orientation changes are a version 1 source in iOS 8.0. } } Perform blocks queued by CFRunLoopPerformBlock; } 

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 .

+20
source

One task after another is added to runloop from different sources; runloop will execute the oldest task on runloop and will not start another task until the call for this task returns.

+1
source
  • User interaction processing
  • UI components call setNeedsLayout and setNeedsDisplay if they need to be updated
  • The layout is done using layoutSubviews (indirectly called layoutSublayers )
  • Painting is done using drawRect and drawInContext: A call is made
  • dispatch_async
  • Your timer with a delay of 0.000001 seconds can be executed before or after dispatch_async . Hard to say.

1 and 2 are actually mixed because basically user interaction causes changes to the user interface, causing setNeedsLayout and setNeedsDisplay somewhere.

Order 1, 2, 3, and 4 are clearly defined. 5 should also occur after this. NSTimer depends on various circumstances - you should not rely on what it called before or after dispatch_async called, but most likely it will be executed after drawing is completed.

+1
source

Source: https://habr.com/ru/post/1203932/


All Articles