[TOC]
基础优化方向
使用 ARC 管理内存
-
在正确的地方使用 reuseIdentifier
- tableview 用 tableView:cellForRowAtIndexPath: 为 rows 分配 cells 的时候,他的数据应该重用自 UITableViewCell
尽量将 views 设置为不透明(Opaque)
避免过于庞大的 XIB
-
不要阻塞主线程
用于不要是主线程承担过多。因为UIKit在主线程上做所有工作,
渲染、管理触摸反应、回应输入等都需要在主线程上完成
-
大部分阻碍主线程的情形是你的APP在做一些涉及到读写外部资源的 I/O 操作,比如存储或者网络:
-
不要在 viewWillAppear 中做费时的操作
- viewWillAppear,在 view 显示之前被调用,处于效率考虑,方法中不要处理复杂费时操作。
- 只能在该方法中设置 view 的显示属性之类的简单事情,例如背景色、字体等。否则,会明显感觉到 view 有卡顿或者延迟。
图片处理
在 ImageView 中调整图片大小。
-
保证图片大小和 UIImageView 大小相同。
如果要在 UIIImageView 中显示一个来自 bundle 的图片,你应保证图片的大小和 UIImageView 大小相同。
在运行中缩放图片是耗费资源的,特别是 UIImageView 嵌套在 UIScrollView 中的情况下。
处理网络图片。如果图片是从远端服务器加载的,你无法控制图片大小。那么就在下载完成之后,最好是在
background thread
中,缩放一次,然后在 UIImageView 中使用缩放之后的图片。
-
imageNamed 初始化:加载图片、并缓存
- imageNamed 默认加载图片成功后,内存中会缓存图片。
- 这个方法用一个指定的名字在系统缓存中查找并返回一个图片对象。
- 如果缓存中没有找到响应的图片对象,则从指定地方加载图片,然后缓存对象,并返回这个而图片对象。
-
imageWithContentsOfFile 初始化
- imageWithContentOfFile 则仅只加载、不缓存。
- 如果加载一张大图并且使用一次,用 imageWithContentsOfFile 是最好的,这样 CPU 不需要做缓存,可以节约时间。
-
具体使用哪一种需要根据应用场景加以区分,UIImage 虽小,但是是使用非常多的元素,也会有比较显著的问题。
- “不以善小而不为,不以恶小而为之”
- “不积跬步无以至千里”
-
UIImageView
- 在性能的范围之内,直接对 UIImageView 设置圆角是不会触发离屏渲染的,但同时给 UIImageView 设置背景色肯定会触发离屏渲染
- 触发离屏渲染跟 .png.jpg 格式无关
UITableView 优化
view相关操作
- 正确使用 reuseIdentifier 来重用 cells
- 减少subview的数量
- 尽量使所有的 view opaque(不透明),包括cell自身
- 使用 shadowPath 来画阴影
- 避免渐变、图片缩放
缓存相关操作
- 缓存行高
- 尽量不要使用 cellForRowAtIndexPath。如果要使用,只用一次,然后缓存结果
- 尽量使用 rowHeight、sectionHeight、sectionHeaderHeight 来设定固定的行高,不要请求delegate
数据加载相关操作
- 如果 cell 内实现的内容来自 web,使用异步加载,缓存请求结果
- 使用正确的数据结果来存储数据
CPU 和 GPU 层优化
本质上是降低 CPU、GPU的工作,从这两个方面入手去提升性能
-
CPU做了什么事
- 逻辑运算
- 对象的创建
- 对象属性的调整、布局计算、文本的计算和排版
- 图片的格式转码和解码
- 图像的绘制
-
GPU做了什么事
- 数学运算
- 纹理绘制
CPU 层面的优化
- 尽量用轻量级的对象,比如用不到时间处理的地方,可以考虑使用 CALayer 取代 UIView
- 不要频繁调用 UIView 的相关属性,例如 frame、bounds、transform 等属性,尽量减少不必要的修改
- 尽量提前计算好布局,在有需要时一次性调整对应的属性,不要对此修改属性
- Autolayout 会比直接设置 frame 消耗更多的 CPU 资源
- 图片的 size 最好刚好更 UIImageView 的 size 保持一致
- 控制一下 线程的最大并发数量
- 尽量把耗时操作放到子线程
- 文本处理(尺寸计算、绘制)
- 图片处理(解码、绘制)
GPU 层面的优化
- 尽量避免短时间内大量图片的显示,尽可能你将多张图片合成一张进行显示
- GPU 能处理的最大纹理尺寸是
4096x4096
,一旦超过这个尺寸,就会占用 CPU 资源进行处理,所以纹理尽量不要超过这个尺寸 - 尽量减少视图数据和层次
- 减少透明的视图(alpha<1),不透明的就设置 opaque 为 YES(打开绘制优化开关)
- 尽量避免出现离屏渲染
离屏渲染优化
如何高性能的画一个圆角?
视图和圆角的大小对帧率并没有什么影响,数量最核心的影响因素
这个是我们最常规的设置方式,但不可取,因为会触发离屏渲染
- 如果能够只用 cornerRadius 解决问题,就不用优化
- 如果必须设置 maskToBounds,可以参考圆角视图的数量,如果数量少(一页只有几个)也可以考虑不用优化。
- UIImageView 的圆角通过直接截取图片实现,其他视图的圆角可以通过 Core Graphics 画出圆角矩阵实现
- 通过 CoreGraphic 画一个圆角,不会触发离屏渲染
什么是 “离屏渲染”?
离屏渲染就是在当前屏幕缓冲区外,新开辟一个缓冲区进行操作。
-
在 OpenGL 中,GPU 有2中渲染方式
- On-Screen Rendering:当前屏幕渲染,在当前用于显示的屏幕缓冲区进行渲染操作
- Off-Screen Rendering:离屏渲染,在当前屏幕缓冲区以外开辟一个缓冲区进行渲染操作
为何要避免离屏渲染?
CPU、GPU 在绘制渲染视图是做了大量的工作。离屏渲染发生在 GPU 层面上,会创建新的渲染缓缓冲区,会触发 OpenGL 的多通道渲染管线,图像上下文切换会早晨额外的开销,增加 GPU 工作量。如果CPU、GPU累计耗时 16.67 ms还没有完成,就会造成卡顿掉帧。
圆角属性、蒙层遮罩 都会触发离屏渲染。指定以上属性,标记了它在新的图形上下文中,在未愈合之前,不可以用于显示的时候就触发了离屏渲染。
离屏渲染消耗性能的原因?
需要创建新的缓冲区
-
离屏渲染的整个过程,需要多次切换上下文环境
- 上下文先从当前屏幕(On-Screen)切换到离屏(Off-Screen)
- 等离屏渲染结束后,将离屏缓冲区的结果显示到屏幕上
- 再将上下文切换到当前屏幕
哪些操作会触发离屏渲染?
-
layer.shouldRasterzize:光栅化
- 光栅化概念:将图转化为一个个栅格组成的图像
- 光栅化特点:每个元素对应帧缓冲区的一像素
- 光栅化限制:系统给光栅化限制了内存,如果超过就会触发离屏渲染,所以cell中一般不使用
mask:遮罩
shadows:layer.shadowXXX,如果设置了 layer.shadowPath 就不会产生离屏渲染
group opacity:不透明,layer属性
edge antialiasing:抗锯齿
cornerRadius:圆角。同时设置 layer.masksToBounds = YES; layer.cornerRadius 大于0,考虑通过 CoreGraphics 绘制圆角,或者让美工提供圆角图片
渐变
drawRect
离屏渲染 VS CPU 渲染
上面说到了,所有不在 GPU 的当前屏幕缓冲区进行的渲染都叫离屏渲染,还有另外一种特殊的离屏渲染,叫“CPU 渲染”。
-
CPU渲染:
- 如果重写了 drawRect 方法,并且使用任何 Core Graphics 的技术,进行了绘制操作,就涉及到了 CPU渲染
- 整个渲染过程由 CPU 在 APP 内同步完成,渲染得到的 bitmap 最后交给 GPU 用于显示
由于 GPU 的浮点运算能力比 CPU 强,CPU 渲染的效率可能不如离屏渲染。但如果只是实现一个简单的效果,直接使用CPU渲染就可以,因为离屏渲染涉及到缓冲区创建以及上下文切换等耗时操作。
如何检测离屏渲染?
-
模拟器:
Debug->Color Off-screen Rendered
可以看到图片
亮黄色
部分就是离屏渲染的视图 -
真机:xcode9之后可以不用 Instrument 了,运行程序之后:
Debug -> View Debugging -> Rendering -> Color Offscreen-Rendered Yellow
离屏渲染的解决思路
-
预排班,提前计算
在接收到服务器端返回的数据后,尽量将 CoreText 排版的结果、单个空间的高度、cell 整体的高度提前计算好,将其存储在模型的属性中。需要使用时,知己耳聪模型中往外取,避免了计算的过程。
尽量少用 UILabel,可以使用 CALayer。避免使用 AutoLayout 的自动布局,才去纯代码的方式
-
预渲染,提前绘制
圆形的图标可以提前在:接收到网络返回数据时,后台线程进行处理,直接存储在模型数据中,回到主线程后直接调用
避免使用 CALayer 的 Border、Corner、Shadow、Mask 等技术,这些都会触发离屏渲染
异步绘制
全局并发线程
高效的图片异步加载
图层混合优化
-
怎么检测图层混合
在下面 instrument 工具介绍中有给出
-
怎么避免图层混合
- 确保控件的 Opaque 属性设置为 true
- 确保控件的 背景色 和父视图的背景色一致 且不透明
- 如无特殊需要,不要设置低于 1 的 alpha 值
- 确保 UIImage 没有alpha 通道
- UILabel iOS8以后设置背景色为非透明 和 设置 label.layer.masksToBounds=YES,就可以让label只渲染给定的size区域,解决 UILabel 的图层混合问题
instrument 工具
上面讲到了如何检测离屏渲染,除了离屏渲染 Rendering
还有其他几种调试类型
-
Color Blended Layer :图层混合
表示区域使用多种混合图层,(图层混合:由于多 UI/Layer 叠加,如果有透明或半透明颜色时,CPU就会去计算最终显示的颜色,中间就涉及很很多多余计算。
颜色标识
- 红色:混合图层 - 绿色:没有使用混合
调优
减少红色区域 1. 设置 Opaque 属性为 YES 2. 给 view 设置一个不透明的颜色
-
Color Hits green and Misses Red:光栅化(缓存layer)
检测 layer 是否使用 shouldRasterize,为true开启光栅化(默认),光栅化会将layer预先渲染为位图 bitmap,然后缓存,从而提高性能。
颜色标识
- 红色:光栅化 - 绿色:未光栅化
调优
使用内容不变的layer,不适合tableview,会造成多余离屏渲染降低性能(原因:系统给光栅化限制了内存,如果超过就会离屏渲染)
-
Color copied Images 图片格式检测与复制
Shows images that are copied by Core Animation in blue
,苹果官方注释说 被拷贝给CPU进行转化的图片显示为绿色。如果 GPU 不支持当前图片的颜色格式,那么就会将图片交给 CPU 预先进行格式转化,并且这张图片标记为绿色。
GPU 只解析 32 bit 的颜色格式,如果使用 Color Copied Images 调试,图片是蓝色
颜色标识
- 蓝色:需要赋值
扩展
32bit 指图片颜色深度,用“位”来表示,用来表示显示颜色数量,例如一个图片支持256种颜色,那么就需要256个不同的值来表示不同的颜色,就需要0-255,二进制表示就是从 00000000-11111111,一共需要 8位 二进制数,所以**颜色深度是 8**。通常32bit色彩中使用3个bit表示R(红)G(绿)B(蓝),还有一个 8bit 表示 Alpha(透明度)
-
Color misaliged Images:图片尺寸匹配
目标像素与源像素不对齐的图片,比如图片大小和 UIImageView 大小不一致
颜色标识
- 洋红色:图片没有像素对齐 - 黄色:图片缩放
优化
尽量匹配大小
-
Color Compositing Fast-Path Blue:快速路径
标记由硬件绘制的路径,显示蓝色,越多越好。可以直接对OpenGL绘制的图像高亮。
颜色标识
- 蓝色
优化
一般不做检测
-
Flash updated Regions:重绘区域
对重绘区域高亮为黄色,会使用 CoreGraphic 绘制,越小越好
颜色标识
- 黄色
-
Color Immediately:颜色刷新频率
当执行颜色刷新的时候移除 10ms 的延迟,因为可能在特定情况下你不需要这些延迟,所以使用此选项加快颜色刷新的频率。
一般用不到
opaque 绘制优化
这个值不是决定视图是否是透明的,而是给绘制系统提供一个个性能优化的开关
- YES:不透明。UIView 默认为 YES
- NO:透明。UIButton、UILabel等子类默认为 NO
red + green = yellow
在第一篇文章中介绍了一个公式:
R = S + D * (1 - Sa)
- R:像素RGB
- S:源色彩(顶端纹理)
- D:目标颜色(低一层的纹理)
- Sa:源色彩的透明度
- redView = (1, 0, 0, 1)
- greenView = (0, 1, 0, 0.5)
R = 1 + 0 * (1-0.5)
G = 0 + 1 * (1-0.5)
B = 0 + 0 * (1-0.5)
A = 1 + 0.5 * (1-0.5)
- 得出结果是:(1, 0.5, 0, 1)
而当 Sa=1 时,R=S,也就是说。这个时候不管目标颜色是什么,GPU 都不需要做任何计算合成。只需要简单的从这个层进行拷贝,节省了GPU大量的工作量。