本文章将记录有关 iOS App的卡顿原因、优化和UI绘制原理,如有错误欢迎指出~
图像显示原理
在使用App中,首先映入眼帘的就是图像,它也是App传递思想和精神的核心。可以说,没有图像,App将不复存在。
先来了解下图像的显示原理:
通常来说,CPU、GPU、显示器通过总线连接协同工作
CPU进行一系列的工作,输出一个位图(
Bitmap
)在合适的时机,经由总线把Bitmap提交给GPU
GPU进行图层渲染,将结果放入帧缓存区(FrameBuffer)中
视频控制器根据垂直同步信号(VSyn信号),在指定时间之前提取帧缓存区中的图像,显示到手机屏幕上
掉帧、卡顿的产生及优化
我们已经知道系统是如何生成图像,并展示给用户了。接下来讨论下用户关注的另一个大问题:流畅性。
通常来说, 页面滑动的流畅性是60FPS(画面每秒传输帧数),即每秒钟刷新六十帧画面,16.7(1/60)毫秒刷新一帧画面。如下示意图
如果CPU和GPU无法在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的相关属性。
说到底,UIView 和 CALayer 遵循单一职责原则:
UIView为CALayer提供显示绘制内容的容器,以及负责处理触摸等事件。
CALayer负责绘制内容
UIView绘制流程图
当我们调用
[UIView setNeedsDisplay]
方法时,并没有执行立即执行绘制工作。而是马上调用
[view.layer setNeedsDisplay]
方法,给当前layer
打上脏标记。在当前RunLoop快要结束的时候调用
layer
的display
方法,来进入到当前视图的真正绘制当中。-
在
layer
的display
方法内部,系统会判断layer
的layer.delegate
是否实现了displayLayer:
方法NO,则执行系统的绘制流程
YES,则会进入异步绘制的入口
系统绘制流程图
在layer内部会创建一个backing store,我们可以理解为CGContextRef上下文。
-
判断layer是否有delegate:
YES,则会执行
[layer.delegate drawLayer:inContext]
(这个方法的执行是在系统内部执行的),在这个方法中会调用view的drawRect:
方法,也就是我们重写view的drawRect:
方法才会被调用NO,会调用layer的
drawInContext:
方法,也就是我们可以重写的layer的该方法,此刻会被调用到。
最后把绘制完的backing store(可以理解为位图)提交给GPU。
异步绘制时序图
异步绘制的入口在[layer.delegate displayLayer]
,通过实现layer的代理方法
- 生成对应的位图(bitmap);
- 将
bitmap
赋值给layer.content
属性;
总结
如果界面出现卡顿,可以从 CPU 和 GUP两个层面去优化。
UIView本身并不能绘制内容,而只是提供一个显示内容的容器,具体的绘制工作是由它持有的CALayer来完成的。