iOS|iOS 性能优化_AsyncDisplayKit 初探

【iOS|iOS 性能优化_AsyncDisplayKit 初探】AsyncDisplayKit 是 Facebook 开源的用于保持 iOS 界面流畅的库。

  1. ASDK 的基本原理

    iOS|iOS 性能优化_AsyncDisplayKit 初探
    文章图片

    ASDK 认为,阻塞主线程的任务,主要分为以上三大类,文本和布局的计算、渲染、解码、绘制都可以通过各种方式异步执行,但 UIKit 和 CoreAnimation 相关操作必须在主线程执行。ASDK 的主要任务,就是将这些任务从主线程挪走,而挪不走的,就尽量封装优化。
为了达成这一目标,ASDK 尝试对 UIKit 组件进行封装

iOS|iOS 性能优化_AsyncDisplayKit 初探
文章图片

这是常见的 UIView 和 CALayer 的关系:UIView 持有 Layer 用于显示,View 中的大部分显示属性实际上是从 Layer 映射而来的;Layer 的 delegate 是 UIView,当其属性改变、动画产生时,View 能够得到通知。UIView 和 CALayer 不是线程安全的,并且只能在主线程创建、访问和销毁。
![](http://upload-images.jianshu.io/upload_images/4653622-ace8647c111ee2d1.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/500)

ASDK 为此创建了 ASDisplayNode 类,包括了常见属性(比如 frame/bounds/alpha/transform/backgroundColor/superNode/subNodes)等,然后它用 UIView->CALayer 的方式,实现了 ASNode->UIView 这样的一个关系。
iOS|iOS 性能优化_AsyncDisplayKit 初探
文章图片

当不需要响应触摸事件时,ASDisplayNode 可以被设置为 layer backed,即 ASDisplayNode 充当了原来的 View 的功能,节省了更多资源。
与 UIView 和 CALayer 不同,ASDisplayNode 是线程安全的,它可以在后台线程创建和修改。Node 刚创建的时候,并不会在内部新建 UIView 和 CALayer,直到第一次在主线程访问 UIView 和 CALayer 属性时,它才会在内部生成相应对象。当它的属性(frame/transform)改变后,它并不会立即同步到它持有的 View 或者 Layer 上,而是把改变的属性保存到内部的一个中间变量,稍后需要的时候再通过某个机制一次性设置到内部的 View 和 Layer。
  1. ASDK 的图层预合成
    有时候一个 Layer 会包含许多 sub-Layer,而这些 sub-Layer 并不需要响应触摸事件,也不需要进行动画和位置调整。ASDK 为此实现了一个叫做 pre-composing 的技术,可以把这些 sub-layer 合成渲染为一张图片。开发时,ASNode 已经替代了 UIView 和 CALayer;直接使用各种 Node 并设置为 layer backed 后,ASNode 甚至可以使用预合成来避免创建内部的 UIView 和 CALayer。
    通过这种方式,把一个大的层级,通过一个大的绘制方法绘制到一张图上,性能会获得很大提升。CPU 也避免了创建 UIKit 对象的资源消耗,GPU 避免了多张 Texture 合成和渲染的消耗,更少的 bitmap 也意味着更少的内存占用。
  2. ASDK 异步并行开发
    自4S 开始,苹果移动设备都已经是双核 CPU 以上 ,充分利用多核的优势、并发执行任务对保持界面流畅有很大作用。ASDK 把布局计算、文本排版、图片/文本/图形渲染等操作都封装成较小的任务,并利用 GCD 异步并发执行。如果开发者使用了 ASNode 相关的控件,那么这些并发操作会自动在后台进行,无需进行过多配置。
  3. Runloop 任务分发
    Runloop Work Distribution 是 ASDK 一个比较核心的技术。ASDK 的介绍视频和文档中都没有详细介绍,但是网上关于 Runloop 的博客很多,在这里无需赘述。

    iOS|iOS 性能优化_AsyncDisplayKit 初探
    文章图片
iOS 的显示系统是由 VSync 信号驱动的,VSync 由硬件时钟生成,每秒发出60次(这个值取决于设备,iPhone 上通常是 59.97 次)。iOS 图形服务收到 VSync 信号后,会通过 IPC 通知的 App 内,App 的 Runloop 在启动后会注册对应的 CFRunloopSource 通过 math_port 传过来的时钟信号通知,随后 Source 的回调会驱动整个 App 的动画与现实。
Core Animation 在 Runloop 中注册了一个 Observer,监听了 BeforeWaiting 和 Exit 事件,这个 Observer 的优先级是 200 0000,低于其他常见的 Observer。当一个触发事件到来时,Runloop 被唤醒,App 中的代码会执行一些操作,比如创建和调整视图层级
设置 UIView 的 frame、修改 CALayer 的透明度、为视图添加一些动画;这些操作最终会被 CALayer 捕获,并通过 CATransaction 提交到一个中间状态去(CATransaction 的文档中有提到这写内容,但并不完整)。当上面的所有操作结束后,Runloop 即将进入休眠或退出时,关注该事件的 Observer 都会得到通知,这时 CA 注册的那个 Observer 就会在回调中把所有的中间状态合并提交到 GPU 去显示;如果此处有动画,CA 会通过 DisplayLink 等机制多次触发相关流程。
ASDK 在此处模拟了 CoreAnimation 的这个机制,所有针对 ASNode 的修改和提交,总有些任务必须放到主线程中去执行的。当出现这种任务的时候,ASNode 会把任务用 ASASyncTransaction(Group)封装并提交到一个全新容器中去。ASDK 也在 Runloop 中注册了一个Observer,监听的事件和CA一样,但是优先级比 CA 要低。在 Runloop 进入休眠前,CA 处理完事件后,ASDK 就会执行该 loop 内提交的所有任务。具体代码见ASAsyncTransactionGroup。
通过这种机制,ASDK 可以在合适的机会把同步、异步的操作同步到主线程中去,并且能获得不错的性能。
  1. 其他
    ASDK 中还封装了许多高级的功能,比如滑动列表的预加载、v2.0 添加新的布局模式等。
    ASDK 是一个很庞大的库,它本身并不推荐你将整个 App 全部改为 ASDK 驱动,把最需要提升交互性能的地方用 ASDK 进行优化就足够了。

    推荐阅读