前言html
在软件开发领域里常常能听到这样一句话,“过早的优化是万恶之源”,不要过早优化或者过分优化。我认为在编码过程当中时刻注意性能影响是有必要的,但凡事都有个度,不能为了性能耽误了开发进度。在时间紧急的状况下咱们每每采用“quick and dirty”的方案来快速出成果,后面再迭代优化,即所谓的敏捷开发。与之相对应的是传统软件开发中的瀑布流开发流程。ios
卡顿产生的缘由缓存
image性能优化
在 iOS 系统中,图像内容展现到屏幕的过程须要 CPU 和 GPU 共同参与。CPU 负责计算显示内容,好比视图的建立、布局计算、图片解码、文本绘制等。随后 CPU 会将计算好的内容提交到 GPU 去,由 GPU 进行变换、合成、渲染。以后 GPU 会把渲染结果提交到帧缓冲区去,等待下一次 VSync 信号到来时显示到屏幕上。因为垂直同步的机制,若是在一个 VSync 时间内,CPU 或者 GPU 没有完成内容提交,则那一帧就会被丢弃,等待下一次机会再显示,而这时显示屏会保留以前的内容不变。这就是界面卡顿的缘由。微信
所以,咱们须要平衡 CPU 和 GPU 的负荷避免一方超负荷运算。为了作到这一点,咱们首先得了解 CPU 和 GPU 各自负责哪些内容。app
imageasync
上面的图展现了 iOS 系统下各个模块所处的位置,下面咱们再具体看一下 CPU 和 GPU 对应了哪些操做。ide
CPU 消耗型任务函数
布局计算工具
布局计算是 iOS 中最为常见的消耗 CPU 资源的地方,若是视图层级关系比较复杂,计算出全部图层的布局信息就会消耗一部分时间。所以咱们应该尽可能提早计算好布局信息,而后在合适的时机调整对应的属性。还要避免没必要要的更新,只在真正发生了布局改变时再更新。
对象建立
对象建立过程伴随着内存分配、属性设置、甚至还有读取文件等操做,比较消耗 CPU 资源。尽可能用轻量的对象代替重量的对象,能够对性能有所优化。好比 CALayer 比 UIView 要轻量许多,若是视图元素不须要响应触摸事件,用 CALayer 会更加合适。
经过 Storyboard 建立视图对象还会涉及到文件反序列化操做,其资源消耗会比直接经过代码建立对象要大很是多,在性能敏感的界面里,Storyboard 并非一个好的技术选择。
对于列表类型的页面,还能够参考 UITableView 的复用机制。每次要初始化 View 对象时先根据 identifier 从缓存池里取,能取到就复用这个 View 对象,取不到再真正执行初始化过程。滑动屏幕时,会将滑出屏幕外的 View 对象根据 identifier 放入缓存池,新进入屏幕可见范围内的 View 又根据前面的规则来决定是否要真正初始化。
Autolayout
Autolayout 是苹果在 iOS6 以后新引入的布局技术,在大多数状况下这一技术都能大大提高开发速度,特别是在须要处理多语言时。好比阿拉伯语下布局是从右往左,经过 Autolayout 设置 leading 和 trailing 便可。
可是 Autolayout 对于复杂视图来讲经常会产生严重的性能问题,对于性能敏感的页面建议仍是使用手动布局的方式,并控制好刷新频率,作到真正须要调整布局时再从新布局。
文本计算
若是一个界面中包含大量文本(好比微博、微信朋友圈等),文本的宽高计算会占用很大一部分资源,而且不可避免。
一个比较常见的场景是在 UITableView 中,heightForRowAtIndexPath这个方法会被频繁调用,即便不是耗时的计算在调用次数多了以后也会带来性能损耗。这里的优化就是尽可能避免每次都从新进行文本的行高计算,能够在获取到 Model 数据后就根据文本内容计算好布局信息,而后将这份布局信息做为一个属性保存到对应的 Model 中,这样在 UITableView 的回调中就能够直接使用 Model 中的属性,减小了文本的计算。
文本渲染
屏幕上能看到的全部文本内容控件,包括 UIWebView,在底层都是经过 CoreText 排版、绘制为 Bitmap 显示的。常见的文本控件 (UILabel、UITextView 等),其排版和绘制都是在主线程进行的,当显示大量文本时,CPU 的压力会很是大。
这一部分的性能优化就须要咱们放弃使用系统提供的上层控件转而直接使用 CoreText 进行排版控制。
Wherever possible, try to avoid making changes to the frame of a view that contains text, because it will cause the text to be redrawn. For example, if you need to display a static block of text in the corner of a layer that frequently changes size, put the text in a sublayer instead.
上面这段话引用自 iOS Core Animation: Advanced Techniques,翻译过来的意思就是说包含文本的视图在改变布局时会触发文本的从新渲染,对于静态文本咱们应该尽可能减小它所在视图的布局修改。
图像的绘制
图像的绘制一般是指用那些以 CG 开头的方法把图像绘制到画布中,而后从画布建立图片并显示的过程。前面的模块图里介绍了 CoreGraphic 是做用在 CPU 之上的,所以调用 CG 开头的方法消耗的是 CPU 资源。咱们能够将绘制过程放到后台线程,而后在主线程里将结果设置到 layer 的 contents 中。代码以下:
- (void)display {
dispatch_async(backgroundQueue, ^{
CGContextRef ctx = CGBitmapContextCreate(...);
// draw in context...
CGImageRef img = CGBitmapContextCreateImage(ctx);
CFRelease(ctx);
dispatch_async(mainQueue, ^{
layer.contents = img;
});
});
}
图片的解码
Once an image file has been loaded, it must then be decompressed. This decompression can be a computationally complex task and take considerable time. The decompressed image will also use substantially more memory than the original.
图片被加载后须要解码,图片的解码是一个复杂耗时的过程,而且须要占用比原始图片还多的内存资源。
为了节省内存,iOS 系统会延迟解码过程, 在图片被设置到 layer 的 contents 属性或者设置成 UIImageView 的 image 属性后才会执行解码过程,可是这两个操做都是在主线程进行,仍是会带来性能问题。
若是想要提早解码,可使用 ImageIO 或者提早将图片绘制到 CGContext 中,这部分实践能够参考 iOS Core Animation: Advanced Techniques
这里多提一点,经常使用的 UIImage 加载方法有 imageNamed 和 imageWithContentsOfFile。其中 imageNamed 加载图片后会立刻解码,而且系统会将解码后的图片缓存起来,可是这个缓存策略是不公开的,咱们没法知道图片何时会被释放。所以在一些性能敏感的页面,咱们还能够用 static 变量 hold 住 imageNamed 加载到的图片避免被释放掉,以空间换时间的方式来提升性能。
GPU消耗型任务
相对于 CPU 来讲,GPU 能干的事情比较单一:接收提交的纹理(Texture)和顶点描述(三角形),应用变换(transform)、混合并渲染,而后输出到屏幕上。宽泛的说,大多数 CALayer 的属性都是用 GPU 来绘制。
如下一些操做会下降 GPU 绘制的性能,
大量几何结构
全部的 Bitmap,包括图片、文本、栅格化的内容,最终都要由内存提交到显存,绑定为 GPU Texture。不管是提交到显存的过程,仍是 GPU 调整和渲染 Texture 的过程,都要消耗很多 GPU 资源。当在较短期显示大量图片时(好比 TableView 存在很是多的图片而且快速滑动时),CPU 占用率很低,GPU 占用很是高,界面仍然会掉帧。避免这种状况的方法只能是尽可能减小在短期内大量图片的显示,尽量将多张图片合成为一张进行显示。
另外当图片过大,超过 GPU 的最大纹理尺寸时,图片须要先由 CPU 进行预处理,这对 CPU 和 GPU 都会带来额外的资源消耗。
视图的混合
当多个视图(或者说 CALayer)重叠在一块儿显示时,GPU 会首先把他们混合到一块儿。若是视图结构过于复杂,混合的过程也会消耗不少 GPU 资源。为了减轻这种状况的 GPU 消耗,应用应当尽可能减小视图数量和层次,而且减小没必要要的透明视图。
离屏渲染
离屏渲染是指图层在被显示以前是在当前屏幕缓冲区之外开辟的一个缓冲区进行渲染操做。
离屏渲染须要屡次切换上下文环境:先是从当前屏幕(On-Screen)切换到离屏(Off-Screen);等到离屏渲染结束之后,将离屏缓冲区的渲染结果显示到屏幕上又须要将上下文环境从离屏切换到当前屏幕,而上下文环境的切换是一项高开销的动做。
会形成 offscreen rendering 的缘由有:
使用阴影时同时设置 shadowPath 就能避免离屏渲染大大提高性能,后面会有一个 Demo 来演示;圆角触发的离屏渲染能够用 CoreGraphics 将图片处理成圆角来避免。
CALayer 有一个 shouldRasterize 属性,将这个属性设置成 true 后就开启了光栅化。开启光栅化后会将图层绘制到一个屏幕外的图像,而后这个图像将会被缓存起来并绘制到实际图层的 contents 和子图层,对于有不少的子图层或者有复杂的效果应用,这样作就会比重绘全部事务的全部帧来更加高效。可是光栅化原始图像须要时间,并且会消耗额外的内存。
光栅化也会带来必定的性能损耗,是否要开启就要根据实际的使用场景了,图层内容频繁变化时不建议使用。最好仍是用 Instruments 比对开启先后的 FPS 来看是否起到了优化效果。
注意:
shouldRasterize = true 时记得同时设置 rasterizationScale
Instruments 使用
image
Instruments 是一系列工具集,咱们这里只演示 Core Animation 的使用。在 Core Animation 选项右下方会看到以下选项,
image
Color Blended Layers
这个选项选项基于渲染程度对屏幕中的混合区域进行绿到红的高亮显示,越红表示性能越差,会对帧率等指标形成较大的影响。红色一般是因为多个半透明图层叠加引发。
Color Hits Green and Misses Red
当 UIView.layer.shouldRasterize = YES 时,耗时的图片绘制会被缓存,并当作一个简单的扁平图片来呈现。这时候,若是页面的其余区块(好比 UITableViewCell 的复用)使用缓存直接命中,就显示绿色,反之,若是不命中,这时就显示红色。红色越多,性能越差。由于栅格化生成缓存的过程是有开销的,若是缓存能被大量命中和有效使用,则整体上会下降开销,反之则意味着要频繁生成新的缓存,这会让性能问题雪上加霜。
Color Copied Images
对于 GPU 不支持的色彩格式的图片只能由 CPU 来处理,把这样的图片标为蓝色。蓝色越多,性能越差。
Color Immediately
一般 Core Animation Instruments 以每毫秒 10 次的频率更新图层调试颜色。对某些效果来讲,这显然太慢了。这个选项就能够用来设置每帧都更新(可能会影响到渲染性能,并且会致使帧率测量不许,因此不要一直都设置它)。
Color Misaligned Images
这个选项检查了图片是否被缩放,以及像素是否对齐。被放缩的图片会被标记为黄色,像素不对齐则会标注为紫色。黄色、紫色越多,性能越差。
Color Offscreen-Rendered Yellow
这个选项会把那些离屏渲染的图层显示为黄色。黄色越多,性能越差。这些显示为黄色的图层极可能须要用 shadowPath 或者 shouldRasterize 来优化。
Color OpenGL Fast Path Blue
这个选项会把任何直接使用 OpenGL 绘制的图层显示为蓝色。蓝色越多,性能越好。若是仅仅使用 UIKit 或者 Core Animation 的 API,那么不会有任何效果。
Flash Updated Regions
这个选项会把重绘的内容显示为黄色。不应出现的黄色越多,性能越差。一般咱们但愿只是更新的部分被标记完黄色。
演示
上述几个选项中经常使用来检测性能的是 Color Blended Layers、Offscreen-Rendered Yellow 和 Color Hits Green and Misses Red。下面我重点演示一下离屏渲染和光栅化的检测,写了一个简单的 Demo 设置了阴影效果,代码以下:
view.layer.shadowOffset = CGSizeMake(1, 1);
view.layer.shadowOpacity = 1.0;
view.layer.shadowRadius = 2.0;
view.layer.shadowColor = [UIColor blackColor].CGColor;
// view.layer.shadowPath = CGPathCreateWithRect(CGRectMake(0, 0, 50, 50), NULL);
shadowPath 没有设置时用 Instruments 检测 FPS 基本在 20 如下(iPhone6设备),设置了 shadowPath 后基本维持在 55 左右,性能提高十分明显。
下面来看一下光栅化的检测,代码以下,
view.layer.shouldRasterize = YES;
view.layer.rasterizationScale = [UIScreen mainScreen].scale;
勾选 Color Hits Green and Misses Red 选项后显示以下:
image
咱们能够看到在静止时缓存都生效了,在快速滑动时缓存基本不起做用,所以是否要开启光栅化仍是得根据具体场景,用 Instruments 检测开启先后的性能来决定。
总结
本文主要总结了性能调优的一些理论知识,后面还介绍了 Instruments 中 Core Animation 的一些性能检测指标用法。性能优化最重要的是要使用工具来检测而不是猜想,先查看是否有离屏渲染等问题,再用 Time Profiler 分析一下耗时的函数调用。修改后再用工具分析是否有改善,一步一步执行,当心仔细。
建议你们也实际动手分析一下本身的应用,加深一下印象,enjoy~
参考资料
https://www.jianshu.com/p/1b5cbf155b31