iOS上runloop中的操作顺序

时间:2014-10-03 12:04:31

标签: ios grand-central-dispatch nsthread nsrunloop

iOS上的操作顺序是什么?

我正在考虑关于

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

作为答案的一个例子我希望收到它可以是这种格式:

  

主要上的dispatch_async会在下一个运行周期之前发生

     

drawRect 在运行周期结束时发生

3 个答案:

答案 0 :(得分:22)

(部分内容是从my answer to a similar question复制的。)

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

CFRunLoopopen-source CoreFoundation package的一部分,因此我们可以准确了解它所包含的内容。运行循环看起来大致如下:

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's callback
            }
            // Interface orientation changes are a version 1 source in iOS 8.0.
        }
    }
    Perform blocks queued by CFRunLoopPerformBlock;
}

核心动画注册kCFRunLoopBeforeWaiting观察者,订单为2000000(虽然没有记录;您可以通过打印[NSRunLoop mainRunLoop].description来计算出来)。此观察者提交当前CATransaction,其中(如有必要)执行布局(updateConstraintslayoutSubviews),然后绘制(drawRect:)。

请注意,运行循环可以在执行BeforeWaiting观察者之前两次评估true中的while(true)。如果它调度计时器或版本1源,并且将块放在主队列上,则运行循环将在调用BeforeWaiting观察者之前两次运行(并且它将同时调度版本0源)。

系统使用版本0源和版本1源的混合。在我的测试中,触摸事件使用版本0源提供。 (您可以通过在触摸处理程序中放置断点来判断;堆栈跟踪包含__CFRunLoopDoSources0。)进入/离开前景等事件通过CFRunLoopPerformBlock发送,因此我不知道是什么类型的来源真的提供了他们。界面方向更改通过版本1源提供。 CFSocket is documented to be a version 0 source.NSURLSessionNSURLConnection可能会在内部使用CFSocket

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

  1. 准备好计时器,
  2. 阻止dispatch_get_main_queue()运行,
  3. 单个版本1来源将被调度到其回调。
  4. 之后,任意数量的版本0源都可以调用它们的回调。

    所以:

    1. 布局总是在绘制之前发生,如果两者都在Core Animation观察者运行时处于挂起状态。 CA观察器在计时器,主队列块或外部事件回调运行后运行。
    2. 主GCD队列具有计时器和版本1源的资历,如果运行循环在循环的前一轮没有排空主队列。
    3. 计时器具有主要队列和版本1来源的资历,如果三者都准备就绪。
    4. 主要队列的资历超过版本1的来源,两者都应该准备就绪。
    5. 另请注意,您可以随时使用layoutIfNeeded请求立即布局。

答案 1 :(得分:1)

另一个任务从各种来源添加到runloop; runloop将执行runloop上最早的任务,并且在该任务的调用返回之前不会启动另一个任务。

答案 2 :(得分:1)

  1. 处理用户互动
  2. UI组件如果需要更新,请致电setNeedsLayoutsetNeedsDisplay
  3. 使用layoutSubviewslayoutSublayers间接调用)
  4. 完成布局
  5. 使用drawRectdrawInContext:
  6. 完成绘画
  7. dispatch_async致电
  8. 延迟0.000001秒的计时器可以在dispatch_async之前或之后执行。很难说。
  9. 1和2实际上是混合的,因为它主要是用户互动,通过在某处调用setNeedsLayoutsetNeedsDisplay来改变用户界面。

    1,2,3和4的顺序是明确定义的。 5之后也应该总是发生。 NSTimer取决于各种情况 - 您不应该在dispatch_async调用之前或之后调用它,但它很可能会在绘制完成后执行。