iOS上的操作顺序是什么?
我正在考虑关于
的时间安排setNeedsLayout
和layoutSubviews
setNeedsDisplay
和drawRect
[NSTimer scheduledTimerWithTimeInterval:0.000001 tar(...)]
dispatch_async(dispatch_get_main_queue(), ^{ /* code */}
作为答案的一个例子我希望收到它可以是这种格式:
主要上的dispatch_async会在下一个运行周期之前发生
drawRect 在运行周期结束时发生
答案 0 :(得分:22)
(部分内容是从my answer to a similar question复制的。)
事实证明,运行循环很复杂,一个简单的问题,例如“在运行周期结束时会发生drawRect:
吗?”并没有简单的答案。
CFRunLoop
是open-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
,其中(如有必要)执行布局(updateConstraints
和layoutSubviews
),然后绘制(drawRect:
)。
请注意,运行循环可以在执行BeforeWaiting观察者之前两次评估true
中的while(true)
。如果它调度计时器或版本1源,并且将块放在主队列上,则运行循环将在调用BeforeWaiting观察者之前两次运行(并且它将同时调度版本0源)。
系统使用版本0源和版本1源的混合。在我的测试中,触摸事件使用版本0源提供。 (您可以通过在触摸处理程序中放置断点来判断;堆栈跟踪包含__CFRunLoopDoSources0
。)进入/离开前景等事件通过CFRunLoopPerformBlock
发送,因此我不知道是什么类型的来源真的提供了他们。界面方向更改通过版本1源提供。 CFSocket
is documented to be a version 0 source.(NSURLSession
和NSURLConnection
可能会在内部使用CFSocket
。
请注意,运行循环的结构是每次迭代时只发生其中一个分支:
dispatch_get_main_queue()
运行,或 之后,任意数量的版本0源都可以调用它们的回调。
所以:
另请注意,您可以随时使用layoutIfNeeded
请求立即布局。
答案 1 :(得分:1)
另一个任务从各种来源添加到runloop; runloop将执行runloop上最早的任务,并且在该任务的调用返回之前不会启动另一个任务。
答案 2 :(得分:1)
setNeedsLayout
和setNeedsDisplay
layoutSubviews
(layoutSublayers
间接调用)drawRect
和drawInContext:
dispatch_async
致电0.000001
秒的计时器可以在dispatch_async
之前或之后执行。很难说。 1和2实际上是混合的,因为它主要是用户互动,通过在某处调用setNeedsLayout
和setNeedsDisplay
来改变用户界面。
1,2,3和4的顺序是明确定义的。 5之后也应该总是发生。 NSTimer
取决于各种情况 - 您不应该在dispatch_async
调用之前或之后调用它,但它很可能会在绘制完成后执行。