iOS上runloop中的操作顺序

hfo*_*sli 7 nsthread grand-central-dispatch nsrunloop ios

iOS上的操作顺序是什么?

我正在考虑时间问题

  • setNeedsLayoutlayoutSubviews
  • setNeedsDisplaydrawRect
  • 触摸识别
  • [NSTimer scheduledTimerWithTimeInterval:0.000001 tar(...)]
  • dispatch_async(dispatch_get_main_queue(), ^{ /* code */}

作为我希望收到的答案的一个例子,它可以采用以下格式:

main上的dispatch_async 在下一个运行周期之前发生

drawRect 在运行周期结束时发生

rob*_*off 23

(部分内容是从我对类似问题的回答中复制而来的.)

事实证明,运行循环很复杂,并且一个简单的问题,例如"在运行drawRect:周期结束时会发生吗?"并没有一个简单的答案.

CFRunLoop开源CoreFoundation包的一部分,所以我们可以看看它究竟是什么.运行循环看起来大致如下:

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;
}
Run Code Online (Sandbox Code Playgroud)

Core Animation注册一个kCFRunLoopBeforeWaiting2000000订单的观察者(虽然没有记录;你可以通过打印来计算出来[NSRunLoop mainRunLoop].description).该观察者提交当前的电流CATransaction(如果需要)执行布局(updateConstraintslayoutSubviews)然后绘制(drawRect:).

需要注意的是运行循环可以评估truewhile(true)执行BeforeWaiting观察家之前的两倍.如果它调度计时器或版本1源,并且将块放在主队列上,则运行循环将在调用BeforeWaiting观察者之前四次运行(并且它将两次调度版本0源).

系统使用版本0源和版本1源的混合.在我的测试中,触摸事件使用版本0源提供.(您可以通过在触摸处理程序中放置断点来判断;堆栈跟踪包含__CFRunLoopDoSources0.)通过调度输入/离开前景等事件CFRunLoopPerformBlock,因此我不知道哪种类型的源实际提供了它们.界面方向更改通过版本1源提供. CFSocket记录为版本0源.(这可能是因为NSURLSessionNSURLConnection使用CFSocket内部.)

请注意,运行循环是结构化的,因此每次迭代时只会发生其中一个分支:

  1. 准备好的计时器开火,
  2. 阻止dispatch_get_main_queue()运行,
  3. 单个版本1源分派给其回调.

之后,任意数量的版本0源都可以调用它们的回调.

所以:

  1. 如果Core Animation观察者运行时两者都处于挂起状态,则布局总是在绘制之前发生.CA观察器在定时器,主队列块或外部事件回调运行后运行.
  2. 如果运行循环在循环的前一轮没有耗尽主队列,主GCD队列具有超过定时器和版本1源的资历.
  3. 定时器具有主要队列和版本1源的资历,如果三者都准备好了.
  4. 主队列具有超过版本1源的资历,两者都应该准备就绪.

另请记住,您可以随时使用请求立即布局layoutIfNeeded.