【问题标题】:Order of operations in runloop on iOSiOS 上的 runloop 中的操作顺序
【发布时间】:2014-10-03 12:04:31
【问题描述】:

iOS 上的操作顺序是什么?

我在考虑时间安排

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

作为我想收到的答案的一个例子,它可能是这种格式:

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

drawRect在运行周期结束时发生

【问题讨论】:

  • 1.setneedsdisplay 和 drawrect 将进入主线程,因为当调用这些方法时,它们将首先执行 2. 计时器目标方法将在其他线程中执行 3. 异步调用将是在第一个两个方法完成后调用
  • @NarasimhaiahKolli 计时器的目标将在安排计时器的线程中被调用,即上述调用的位置。

标签: ios grand-central-dispatch nsthread nsrunloop


【解决方案1】:

(部分内容来自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 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 以 2000000 的顺序注册了一个 kCFRunLoopBeforeWaiting 观察者(尽管没有记录;您可以通过打印 [NSRunLoop mainRunLoop].description 来弄清楚)。这个观察者提交当前的CATransaction,它(如果需要)执行布局(updateConstraintslayoutSubviews)然后绘制(drawRect:)。

请注意,在执行 BeforeWaiting 观察者之前,运行循环可以评估 while(true) 中的 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 源被调度到其回调。

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

所以:

  1. 布局总是在绘制之前发生,如果在核心动画观察器运行时两者都处于未决状态。 CA 观察者在计时器、主队列块或外部事件回调运行后运行。
  2. 主 GCD 队列的优先级高于计时器和版本 1 源,如果运行循环在循环的前一回合没有耗尽主队列。
  3. 如果这三个都准备好了,计时器对主队列和版本 1 源具有优先权。
  4. 主队列的优先级高于版本 1 源,应该都准备好了。

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

【讨论】:

    【解决方案2】:

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

    【讨论】:

    • 好的。那么绘制所有视图的任务是什么时候作为任务添加到runloop上的呢?
    • 这并没有真正回答我的问题 :-)
    【解决方案3】:
    1. 处理用户交互
    2. UI 组件在需要更新时调用 setNeedsLayoutsetNeedsDisplay
    3. 使用layoutSubviews 完成布局(由layoutSublayers 间接调用)
    4. 使用drawRectdrawInContext: 完成绘制
    5. dispatch_async调用已执行
    6. 您的定时器具有0.000001 秒延迟可以在dispatch_async 之前或之后执行。很难说。

    1 和 2 实际上是混合的,因为它主要是用户交互通过在某处调用 setNeedsLayoutsetNeedsDisplay 导致 UI 发生变化。

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

    【讨论】:

      猜你喜欢
      • 2016-02-15
      • 1970-01-01
      • 2015-09-11
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-10-17
      • 2013-06-09
      相关资源
      最近更新 更多