iOS上runloop中的操作顺序 [英] Order of operations in runloop on iOS

查看:183
本文介绍了iOS上runloop中的操作顺序的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

iOS上的操作顺序是什么?



我正在考虑时间问题




  • setNeedsLayout layoutSubviews

  • setNeedsDisplay drawRect

  • 触摸识别

  • [NSTimer scheduledTimerWithTimeInterval:0.000001 tar(...)]

  • dispatch_async(dispatch_get_main_queue(),^ { / * code * /}






作为示例我希望收到的答案可能采用以下格式:


主要上的 dispatch_async发生之前下一个runcycle



drawRect 在运行周期结束时发生



解决方案

(部分内容复制自我对类似问题的回答。)



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

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

  while(true){
调用kCFRunLoopBeforeTimers观察者回调;
调用kCFRunLoopBeforeSources观察者回调;
执行CFRunLoopPerformBlock排队的块;
调用已发出信号的每个版本0 CFRunLoopSource的回调;
//触摸事件是iOS 8.0中的版本0源。
// CFSocket是版本0源。
if(调用任何版本0源回调){
执行CFRunLoopPerformBlock新排队的块;
}
if(我没有在最后一次迭代中排空主队列
并且主队列有任何块等待)
{
while(主队列有块){
执行主队列上的下一个块
}
} else {
调用kCFRunLoopBeforeWaiting观察者回调;
// Core Animation使用BeforeWaiting观察器来执行布局和绘图。
等待CFRunLoopSource发出信号
或者用于计时器来触发
OR以便将一个块添加到主队列中;
调用kCFRunLoopAfterWaiting观察者回调;
if(事件是一个计时器){
调用CFRunLoopTimer回调计时器,现在应该被解雇
}否则if(事件是一个到达主队列的块){
while(主队列有块){
执行主队列上的下一个块
}
} else {
查找事件
的版本1 CFRunLoopSource if(我发现版本1源){
调用源代码的回调
}
//接口方向更改是iOS 8.0中的版本1源。
}
}
执行CFRunLoopPerformBlock排队的块;
}

Core Animation注册 kCFRunLoopBeforeWaiting 订单为2000000的观察者(虽然没有记录;您可以通过打印 [NSRunLoop mainRunLoop] .description 来计算出来)。此观察者提交当前的 CATransaction ,其中(如果需要)执行布局( updateConstraints layoutSubviews )然后绘图( drawRect:)。



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



系统使用版本0源和版本1源的混合。在我的测试中,触摸事件使用版本0源提供。 (您可以通过在触摸处理程序中放置断点来判断;堆栈跟踪包含 __ CFRunLoopDoSources0 。)通过 CFRunLoopPerformBlock <调度进入/离开前台等事件/ code>,所以我不知道究竟是什么类型的源提供它们。界面方向更改通过版本1源提供。 CFSocket 被记录为版本0源。(很可能 NSURLSession NSURLConnection 在内部使用 CFSocket 。)



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


  1. 准备好的计时器开火,

  2. 阻止 dispatch_get_main_queue()运行,

  3. 单个版本1来源分派到它的回调。

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



所以:


  1. 布局总是在绘制之前发生,如果两者在Core Animation观察者运行时都处于未决状态。 CA观察器在计时器,主队列块或外部事件回调运行后运行。

  2. 主GCD队列具有计时器和版本1源的资历,如果运行循环没有在循环的前一个循环中耗尽主队列。

  3. 计时器具有主队列和版本1源的资历,如果三者都准备就绪。

  4. 主队列的资历超过了版本1的来源,两者都准备就绪。

还记得那个您可以随时使用 layoutIfNeeded 请求立即布局。


What is the order of operations on iOS?

I'm thinking sepcifically about timing of

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

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

dispatch_async on main Happens before the next runcycle

drawRect Happens at the end of the runcycle

解决方案

(Parts of this are copied from my answer to a similar question.)

It turns out that the run loop is complicated, and a simple question like "Does drawRect: happen at the end of the runcycle?" doesn't have a simple answer.

CFRunLoop is part of the open-source CoreFoundation package, so we can take a look at exactly what it entails. The run loop looks roughly 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's callback
            }
            // Interface orientation changes are a version 1 source in iOS 8.0.
        }
    }
    Perform blocks queued by CFRunLoopPerformBlock;
}

Core Animation registers a kCFRunLoopBeforeWaiting observer with an order of 2000000 (although that is not documented; you can figure it out by printing [NSRunLoop mainRunLoop].description). This observer commits the current CATransaction, which (if necessary) performs layout (updateConstraints and layoutSubviews) and then drawing (drawRect:).

Note that the run loop can evaluate the true in while(true) twice before executing BeforeWaiting observers. If it dispatches timers or a version 1 source, and that puts block on the main queue, the run loop will go around twice before calling the BeforeWaiting observers (and it will dispatch version 0 sources both times).

The system uses a mixture of version 0 sources and version 1 sources. In my testing, touch events are delivered using a version 0 source. (You can tell by putting a breakpoint in a touch handler; the stack trace contains __CFRunLoopDoSources0.) Events like entering/leaving foreground are dispatched through CFRunLoopPerformBlock, so I don't know what kind of source really provides them. Interface orientation changes are delivered through a version 1 source. CFSocket is documented to be a version 0 source. (It's likely that NSURLSession and NSURLConnection use CFSocket internally.)

Note that the run loop is structured so only one of these branches happens on each iteration:

  1. Ready timers fire, or
  2. Blocks on dispatch_get_main_queue() run, or
  3. A single version 1 source is dispatched to its callback.

After that, any number of version 0 sources can call their callbacks.

So:

  1. Layout always happens before drawing, if both are pending when the Core Animation observer runs. The CA observer runs after timers, main queue blocks, or the external event callback have run.
  2. The main GCD queue has seniority over timers and version 1 sources, if the run loop didn't drain the main queue on the prior turn of the loop.
  3. Timers have seniority over the main queue and version 1 sources, should all three be ready.
  4. The main queue has seniority over version 1 sources, should both be ready.

Also remember that you can request immediate layout at any time using layoutIfNeeded.

这篇关于iOS上runloop中的操作顺序的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆