Runloop work distribution (Runloop任务分发)是ASDK使用的比较核心的一种技术。从我测试的情况来看,Runloop任务分发的时候,有优先处理数据逻辑等,而UI的更新会延迟,即使UI更新发生在逻辑任务前。
我创建了一个名为RWD_Test的工程,测试代码如下:
(1)
在下面的for循环前修改testView的frame,run起来后,发现testView frame的改变发生在for循环完成之后。
再看,我们把代码修改一下:
(2)
在for循环后再加一行改变testView frame的代码,run起来后,testView frame改变还是发生在for循环之后,只是testView frame直接改变为for循环后设置的frame,并没有改变两次。
我们再把代码修改一下,把for循环放到子线程执行:
(3)
把for循环逻辑放到子线程后,run起来后,testView frame马上改变了。
以上的现象我们看到了。那原理是啥呢?
iOS 的显示系统是由 VSync 信号驱动的,VSync 信号由硬件时钟生成,每秒钟发出 60 次。iOS 图形服务接收到 VSync 信号后,会通过 IPC 通知到 App 内。App 的 Runloop 在启动后会注册对应的 CFRunLoopSource 通过 mach_port 接收传过来的时钟信号通知,随后 Source 的回调会驱动整个 App 的动画与显示。
Core Animation 在 RunLoop 中注册了一个 Observer,监听了 BeforeWaiting 和 Exit 事件。这个 Observer 的优先级是 2000000,低于常见的其他 Observer。当一个触摸事件到来时,RunLoop 被唤醒,App 中的代码会执行一些操作,比如创建和调整视图层级、设置 UIView 的 frame、修改 CALayer 的透明度、为视图添加一个动画;这些操作最终都会被 CALayer 捕获,并通过 CATransaction 提交到一个中间状态去(CATransaction 的文档略有提到这些内容,但并不完整),如果这段时间有多次更新,最新的会覆盖之前的改变,如现象(2)。当上面所有操作结束后,RunLoop 即将进入休眠(或者退出)时,关注该事件的 Observer 都会得到通知。这时 CA 注册的那个 Observer 就会在回调中,把所有的中间状态合并提交到 GPU 去显示;如果此处有动画,CA 会通过 DisplayLink 等机制多次触发相关流程,现象(1)证明了这一点。
虽然UI的改变和其他的逻辑处理没有优先级这个说法,但是系统还是做了区分,表现出UI更新的优先级显得低些。所以在开发过程中,把一些任务量大的任务放到子线程去处理,有利于提升性能(现象(3))!
此外,我还发现,runloop在每分钟内都会自动启动一次,即使任何事情都没发生。
git:https://github.com/GongZiYuan/RWD_Test