【iOS面试粮食】UI视图—卡顿原因和优化、UI绘制原理

1,586 阅读4分钟

本文章将记录有关 iOS App的卡顿原因、优化和UI绘制原理,如有错误欢迎指出~

图像显示原理

在使用App中,首先映入眼帘的就是图像,它也是App传递思想和精神的核心。可以说,没有图像,App将不复存在。

先来了解下图像的显示原理:

图像显示原理

通常来说,CPU、GPU、显示器通过总线连接协同工作

  • CPU进行一系列的工作,输出一个位图(Bitmap)
  • 在合适的时机,经由总线把Bitmap提交给GPU
  • GPU进行图层渲染,将结果放入**帧缓存区(FrameBuffer)**中
  • 视频控制器根据垂直同步信号(VSyn信号),在指定时间之前提取帧缓存区中的图像,显示到手机屏幕上

掉帧、卡顿的产生及优化

我们已经知道系统是如何生成图像,并展示给用户了。接下来讨论下用户关注的另一个大问题:流畅性

通常来说, 页面滑动的流畅性是60FPS(画面每秒传输帧数),即每秒钟刷新六十帧画面,16.7(1/60)毫秒刷新一帧画面。如下示意图

掉帧的原因

如果CPUGPU无法在**2个VSync信号(垂直同步信号)**之间完成一帧图像内容的提交,则那一帧就会被丢弃(掉帧),而这时显示器还是显示之前的图像,视觉上就会感觉卡顿,就要开始砸手机了。

在这期间,CPU和GPU都在搞些什么事情呢?

VSync信号到来后,系统图形服务会通过 CADisplayLink 等机制通知 App,App 主线程开始

在 CPU 中

  • Layout
    • UI布局
    • 文本计算
  • Display
    • 绘制(drawRect)
  • Prepare
    • 图片解码
  • Commit
    • 提交位图给GPU

GPU赶紧

  • 顶点着色
  • 图元装配
  • 光栅化
  • 片段着色
  • 片段处理
  • 提交到帧缓冲区中

等待下一次 VSync 信号到来时显示到屏幕上。

综上,我们知道,要解决流畅性的问题,可以从CPU、GPU两个层面进行优化。

优化方案可进入传送阵ibireme 大神的iOS 保持界面流畅的技巧文章中的CPU 资源消耗原因和解决方案GPU 资源消耗原因和解决方案 ,这里面包括了开发中的大部分场景,可以帮助我们快速定位卡顿的原因,迅速解决卡顿。

以下是对大神优化方案的小结:

CPU层面

  • 对象的创建、调整、销毁
  • 预排版(布局计算、文本计算)
  • 预渲染(文本等异步绘制,图片编解码等)

GPU层面

  • 文理渲染(避免离屏渲染)
  • 视图混合(减少视图层级)

UIView绘制原理

UIView 表示屏幕上的一块矩形区域,负责渲染区域的内容,并且响应该区域内发生的触摸事件。它在 iOS App 中占有绝对重要的地位,因为 iOS 中几乎所有可视化控件都是 UIView 的子类。

谈到UIView,就不得不让我们谈到 CALayer。每个UIView都持有一个layer(CALayer的实例),layer负责的是绘图部分的功能。UIView更像是一个CALayer的管理器,访问UIView的跟绘图和坐标有关的属性,例如frame,bounds等等,实际上内部都是在访问它所包含的CALayer的相关属性。

说到底,UIViewCALayer 遵循单一职责原则

  • UIView为CALayer提供显示绘制内容的容器,以及负责处理触摸等事件。
  • CALayer负责绘制内容

UIView绘制流程图

858882e7ce773a4e.png

  • 当我们调用[UIView setNeedsDisplay]方法时,并没有执行立即执行绘制工作。

  • 而是马上调用[view.layer setNeedsDisplay]方法,给当前layer打上脏标记。

  • 在当前RunLoop快要结束的时候调用layerdisplay方法,来进入到当前视图的真正绘制当中。

  • layerdisplay方法内部,系统会判断layerlayer.delegate是否实现了displayLayer:方法

    • NO,则执行系统的绘制流程
    • YES,则会进入异步绘制的入口

系统绘制流程图

ca606fee8fd18646.png

  • 在layer内部会创建一个backing store,我们可以理解为CGContextRef上下文。

  • 判断layer是否有delegate:

    • YES,则会执行[layer.delegate drawLayer:inContext](这个方法的执行是在系统内部执行的),在这个方法中会调用viewdrawRect:方法,也就是我们重写viewdrawRect:方法才会被调用
    • NO,会调用layerdrawInContext:方法,也就是我们可以重写的layer的该方法,此刻会被调用到。
  • 最后把绘制完的backing store(可以理解为位图)提交给GPU。

异步绘制时序图

异步绘制的入口在[layer.delegate displayLayer],通过实现layer的代理方法

  • 生成对应的位图(bitmap);

  • bitmap赋值给layer.content属性;

asyn_display.png

总结

如果界面出现卡顿,可以从 CPUGUP两个层面去优化。

UIView本身并不能绘制内容,而只是提供一个显示内容的容器,具体的绘制工作是由它持有的CALayer来完成的。

参考资料

iOS 保持界面流畅的技巧

理解UIView的绘制原理

UI绘制原理