iOS上的runloop中的操作顺序
iOS上的操作顺序是什么?
我正在考虑关于时机
-
setNeedsLayout
和layoutSubviews
-
setNeedsDisplay
和drawRect
- 触摸识别
-
[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
,它(如果需要)执行布局( updateConstraints
和layoutSubviews
),然后绘制( drawRect:
。
请注意,在执行BeforeWaiting观察者之前,运行循环可以在while(true)
两次评估true
。 如果它调度定时器或版本1源,并且将主块放在主队列上,则在调用BeforeWaiting观察器之前,运行循环将转过两次(并且它将两次调度版本0的源)。
系统使用版本0来源和版本1来源的混合。 在我的testing中,触摸事件是使用版本0来源提供的。 (你可以通过在触摸处理程序中放置一个断点来判断;堆栈跟踪包含__CFRunLoopDoSources0
。)通过CFRunLoopPerformBlock
调度诸如进入/离开前景的事件,所以我不知道提供它们的是什么types的源。 界面方向更改通过版本1源提供。 CFSocket
被logging为版本0的来源。 (这很可能是NSURLSession
和NSURLConnection
CFSocket
内部使用CFSocket
。)
请注意,运行循环的结构使得每次迭代中只有一个分支发生:
- 准备好的定时器开火, 或者
-
dispatch_get_main_queue()
上的块运行, 或者 - 单个版本1源被分派到其callback。
之后,任何数量的版本0来源都可以调用它们的callback函数。
所以:
- 布局总是在绘制之前发生,如果Core Animation观察者运行时两者都处于挂起状态。 定时器,主队列块或外部事件callback已经运行后,CA观察器运行。
- 如果运行循环没有排出前一个循环的主队列, 则主GCD队列具有定时器和版本1源的资历。
- 定时器在主要队列和版本1资源上具有资历,如果三者都准备就绪。
- 主要队列拥有比第一版资源更多的资源,都应该准备好。
另外请记住,您可以随时使用layoutIfNeeded
请求立即布局。
一个接一个的任务从各种来源添加到runloop中; runloop将执行runloop上最老的任务,并不会启动另一个任务,直到该任务的调用返回。
- 处理用户交互
- 如果UI组件需要更新,则调用
setNeedsLayout
和setNeedsDisplay
- 使用
layoutSubviews
(由layoutSublayers
间接调用)完成布局, - 绘画是使用
drawRect
和drawInContext:
-
dispatch_async
调用被执行 - 延时为
0.000001
秒的计时器可以在dispatch_async
之前或之后执行。 很难说。
1和2实际上是混合的,因为它主要是通过在某处调用setNeedsLayout
和setNeedsDisplay
在UI中引起更改的用户交互。
1,2,3和4的顺序是明确的。 5也应该随时发生。 NSTimer
依赖于各种情况 – 你不应该在dispatch_async
调用之前或之后调用它,但是它最有可能在绘画完成后被执行。