iOS上的runloop中的操作顺序

iOS上的操作顺序是什么?

我正在考虑关于时机

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

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

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

drawRect在运行周期结束时发生

(这部分是从我的答案复制到类似的问题 。)

事实certificate,运行循环是复杂的,一个简单的问题,如“是否在运行循环结束时发生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 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; } 

核心animation注册一个kCFRunLoopBeforeWaiting观察者的顺序为2000000(尽pipe这没有logging;你可以通过打印[NSRunLoop mainRunLoop].description )来解决它。 这个观察者提交当前的CATransaction ,它(如果需要)执行布局( updateConstraintslayoutSubviews ),然后绘制( drawRect:

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

系统使用版本0来源和版本1来源的混合。 在我的testing中,触摸事件是使用版本0来源提供的。 (你可以通过在触摸处理程序中放置一个断点来判断;堆栈跟踪包含__CFRunLoopDoSources0 。)通过CFRunLoopPerformBlock调度诸如进入/离开前景的事件,所以我不知道提供它们的是什么types的源。 界面方向更改通过版本1源提供。 CFSocket被logging为版本0的来源。 (这很可能是NSURLSessionNSURLConnection CFSocket内部使用CFSocket 。)

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

  1. 准备好的定时器开火, 或者
  2. dispatch_get_main_queue()上的块运行, 或者
  3. 单个版本1源被分派到其callback。

之后,任何数量的版本0来源都可以调用它们的callback函数。

所以:

  1. 布局总是在绘制之前发生,如果Core Animation观察者运行时两者都处于挂起状态。 定时器,主队列块或外部事件callback已经运行后,CA观察器运行。
  2. 如果运行循环没有排出前一个循环的主队列, 主GCD队列具有定时器和版本1源的资历。
  3. 定时器在主要队列和版本1资源上具有资历,如果三者都准备就绪。
  4. 主要队列拥有比第一版资源更多的资源,都应该准备好。

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

一个接一个的任务从各种来源添加到runloop中; runloop将执行runloop上最老的任务,并不会启动另一个任务,直到该任务的调用返回。

  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调用之前或之后调用它,但是它最有可能在绘画完成后被执行。