
麦子学院 2016-06-16 23:27
UIKit如何进行性能调优(续)
回复:0 查看:3104
颜色格式
像素在内存中的布局和它在磁盘中的存储方式并不相同。考虑一种简单的情况:每个像素有
R
、
G
、
B
和
alpha
四个值,每个值占用
1
字节,因此每个像素占用
4
字节的内存空间。一张
1920*1080
的照片
(iPhone6 Plus
的分辨率
)
一共有
2,073,600
个像素,因此占用了超过
8Mb
的内存。但是一张同样分辨率的
PNG
格式或
JPEG
格式的图片一般情况下不会有这么大。这是因为
JPEG
将像素数据进行了一种非常复杂且可逆的转化。
当我们打开
JPEG
格式的图片时,
CPU
会进行一系列运算,将
JPEG
图片解压成像素数据。显然这个工作会消耗不少时间,所以不应该在滑动时进行,我们应该预先处理好图片。借用
WWDC
上的一页
PPT
来说明:

显示流程
Commit Transaction
和
Decode
在同一帧内进行,如果这两个操作的耗时超过
16.67s
,
Draw Calls
就会延迟到下一帧,从而导致
fps
值的降低。下面是
Commit Transaction
的详细流程:

解码与转换
在第三步的
Prepare
中,
CPU
主要处理两件事:
把图片从
PNG
或
JPEG
等格式中解压出来,得到像素数据。
如果
GPU
不支持这种颜色各式,
CPU
需要进行格式转换。
比如应用中有一些从网络下载的图片,而
GPU
恰好不支持这个格式,这就需要
CPU
预先进行格式转化。第三个选项
“Color Copied Images”
就用来检测这种实时的格式转化,如果有则会将图片标记为蓝色。
遗憾的是由于我对图片格式不太了解,也不会使用相关工具,并没有能模拟出触发这个选项的场景。我们要记住的是,如果调试时发现有图片被标记为蓝色,说明图片格式出现了一些问题。
图片大小
第四个选项的使用场景不多,我们直接看一下第五个选项
“Color Misaligned Images”
。它表示如果图片需要缩放则标记为**,如果没有像素对齐则标记为紫色。勾选上这个选项并进行调试,可以看到如下场景:

图片缩放
在
demo
中,每个
UIImageView
的大小都是
180x180
,而只有第二张图片的像素大小是
360x360
。因此除了第二张图片,其他的图片都需要被缩放。图片的缩放需要占用时间,因此我们要尽可能保证无论是本地图片还是从网络或取得图片的大小,都与其
frame
保持一致。
第三个优化是调整所有图片的像素大小以避免不必要的缩放。
离屏渲染
离屏渲染表示渲染发生在屏幕之外,你可能认为这是一句废话。为了真正解释清楚什么是离屏渲染,我们先来看一下正常的渲染通道
(Render-Pass)
:

正常渲染通道
首先,
OpenGL
提交一个命令到
Command Buffer
,随后
GPU
开始渲染,渲染结果放到
Render Buffer
中,这是正常的渲染流程。但是有一些复杂的效果无法直接渲染出结果,它需要分步渲染最后再组合起来,比如添加一个蒙版
(mask)
:

离屏渲染
在前两个渲染通道中,
GPU
分别得到了纹理
(texture
,也就是那个相机图标
)
和
layer(
蓝色的蒙版
)
的渲染结果。但这两个渲染结果没有直接放入
Render Buffer
中,也就表示这是离屏渲染。直到第三个渲染通道,才把两者组合起来放入
Render Buffer
中。离屏渲染意味着把渲染结果临时保存,等用到时再取出,因此相对于普通渲染更占用资源。
第六个选项
“Color Offscreen-Rendered Yellow”
会把需要离屏渲染的地方标记为**,大部分情况下我们需要尽可能避免**的出现。离屏渲染可能会自动触发,也可以手动触发。以下情况可能会导致触发离屏渲染:
重写
drawRect
方法
有
mask
或者是阴影
(layer.masksToBounds, layer.shadow*)
,模糊效果也是一种
mask
layer.shouldRasterize = true
前两者会自动触发离屏渲染,第三种方法是手动开启离屏渲染。
开始调试并勾选
“Color Offscreen-Rendered Yellow”
,会看到这样的场景:

离屏渲染
如果没有进行第二步优化,你会发现
label
也是**。可以看到
tabbar
和
statusBar
也是**,这是因为它们使用了模糊效果。图片也是**,这说明它也进行了离屏渲染,观察源码后发现主要原因是它使用了阴影,接下来我们进行第四个优化,在设置阴影效果的四行代码下面添加一行:
1
|
imgView.layer.shadowPath = UIBezierPath(rect: imgView.bounds).CGPath
|
这行代码制定了阴影路径,如果没有手动指定,
Core Animation
会去自动计算,这就会触发离屏渲染。如果人为指定了阴影路径,就可以免去计算,从而避免产生离屏渲染。
设置
cornerRadius
本身并不会导致离屏渲染,但很多时候它还需要配合
layer.masksToBounds = true
使用。根据之前的总结,设置
masksToBounds
会导致离屏渲染。解决方案是尽可能在滑动时避免设置圆角,如果必须设置圆角,可以使用光栅化技术将圆角缓存起来:
1
2
3
4
5
|
//
设置圆角
label.layer.masksToBounds = true
label.layer.cornerRadius = 8
label.layer.shouldRasterize = true
label.layer.rasterizationScale = layer.contentsScale
|
快速路径
还记得之前将离屏渲染和渲染路径时的示意图么,离屏渲染的最后一步是把此前的多个路径组合起来。如果这个组合过程能由
CPU
完成,就会大量减少
GPU
的工作。这种技术在绘制地图中可能用到。
第七个选项
“Color Compositing Fast-Path Blue”
用于标记由硬件绘制的路径,蓝色越多越好。
变化区域
刷新视图时,我们应该把需要重绘的区域尽可能缩小。对于未发生变化的内容则不应该重绘,第八个选项
“Flash updated Regions”
用于标记发生重绘的区域。一个典型的例子是系统的时钟应用,绝大多数时候只有显示秒针的区域需要重绘:

重绘区域
原文来自:
bestswifter