原文做者:Hello_Vincent
原文地址: https://juejin.im/post/5b72aa...
这一篇文章是iOS性能优化系列文章的的第二篇,主要内容是关于列表流畅度的优化。在具体内容的阐述过程当中会结合性能优化的整体原则进行阐述,因此建议你们在阅读这篇文章前先阅读一下上一篇文章:iOS性能优化系列篇之“优化整体原则”。ios
因为平时工做比较忙,两篇之间的间隔有点久。但这两篇文章出乎我意料地受到了你们的喜欢,因此我但愿后面有时间能把这个系列更新下去,下一步准备写一篇关于iOS内存相关的优化文章。也但愿这篇列表流畅度优化的文章可以给你们带来一点点启示。算法
和上一篇综述性质的文章不一样,这一篇文章工程实用性更强一些,更多的是一些优化技术细节。文中讨论了许多可能影响列表流畅度的因素,因为2018 WWDC里面讲述了大量的关于性能优化相关的内容,所以本文也在相关的内容里面加入2018 WWDC的性能优化部分。缓存
读者可将本体说起的优化手段或者原理应用到本身的项目中去。可是但愿你们在优化过程当中,要结合本身的项目具体问题具体分析,由于本文讨论的影响流畅度的因素,可能并非你的应用流畅性不佳的瓶颈,根据个人经验,大部分流畅的问题都是业务逻辑致使的,反倒什么离屏渲染啊之类你们耳熟能详的流畅度的影响因素在实际项目中并无想象的那么大。若是不经实地测量就盲目应用一些优化手段,可能会致使过分优化,事倍功半。安全
在整体原则篇中提到,五大原则中的其中一个就是要理解优化任务的底层运行机制,由于只有深刻了解底层机制才能更好的有针对性的提出更优的解决方案,因此在进行列表流畅度优化前,咱们必定要弄清楚一个view从建立到显示到屏幕上都经历了那些过程,在这些过程当中那些方面可能会致使性能瓶颈,以及形成卡顿的底层缘由是什么。性能优化
咱们知道iOS设备大部分状况下,屏幕刷新频率是60hz(ProMotion下是120hz),也就是每隔16.67ms会进行一次屏幕刷新。每次刷新时,须要CPU和GPU配合完成一次图像显示。其主要流程以下:服务器
应用内:网络
应用外(render server):多线程
从上面的图中能够看到,在view显示的过程当中,CPU和GPU都各自承担了不一样的任务,CPU和GPU不论哪一个阻碍了显示流程,都会形成掉帧现象。因此优化方法也须要分别对CPU和GPU压力进行评估和优化,在CPU和GPU压力之间找到性能最优的平衡点, 不管过分优化哪一方致使另外一方压力过大都会形成总体FPS性能的降低。而寻找平衡点的过程则因项目特色不一样而不一样,并无一套通用的方法,须要咱们用instrument等性能评测工具,根据实际app的性能度量结果去作优化,不能凭空乱猜。并发
咱们先看table view在滑动过程当中CPU占用的状况。app
从上图能够看出,在滑动过程当中CPU占用特色是:
根据上述特色咱们能够作以下优化:
为何要预加载:
经过预加载咱们但愿达到的CPU理想占用效果以下:
预加载内容:
如何预加载:
iOS10之前,也能够本身实现相似机制,主要利用的机制有:
加载内容:
注意事项:
WWDC 2018中讲到了一个iOS12的底层优化点,苹果工程师在性能调优的时候发现一个致使丢帧的奇怪case,在没有其余后台线程运行、只有滑动的状况下,会比有少许的后台线程的状况更容易掉帧。经过调研CPU的调度算法发现,在仅有滑动的状况下,为了省电,CPU占用会保持比较底,可是这样CPU会花更多的时间来计算,就会致使可能错过这一帧。因此iOS12中,会把UIKit框架上全部的信息(滑动信息以及滑动frame的关键时间点)传递给底层CPU性能控制器,这样CPU能够更智能调度以在frame截止的时机内完成CPU计算。这部分属于系统底层的优化,对于应用开发者只要应用运行在iOS12就能够得到这部分优化。
最终经过多线程,咱们但愿CPU占用达到以下效果:
缓存的内容能够是
这里说的更优的实现方式,主要是指为了实现同一功能或者效果,CPU占用更小的实现方式。这部分包括的内容其实很是多,也很杂。受限于篇幅和水平有限,这里笔者仅罗列一些比较常见的点,并针对其中比较重要的drawRect优化和图片优化内容作进一步的讲解。
下面详细讲下drawRect优化和图片优化
首选使用CAShapeLayer替代drawRect,在大多数场景下,均可以使用CAShapeLayer替代drawRect。两者对比:
在大多数app中,图片绝对是使用最频繁的资源之一,咱们知道磁盘和网络的加载速度和内存比要慢不少,而通常图片都比较大,I/O十分耗时。并且图片还涉及解码,也是一项十分消耗CPU的工做,所以图片的优化对app的性能有着十分关键的做用。谈谈iOS中图片的解压缩
在以前将的优化整体原则的时候,咱们说过须要理解优化对象的运行机制,咱们先了解下图片显示原理:
在main run loop, 提交transaction:
针对上面的过程,咱们的优化手段主要有:
CPU和GPU之因此大不相同,是因为其设计目标的不一样,它们分别针对了两种不一样的应用场景。CPU须要很强的通用性来处理各类不一样的数据类型,同时又要逻辑判断又会引入大量的分支跳转和中断的处理。这些都使得CPU的内部结构异常复杂。而GPU面对的则是类型高度统一的、相互无依赖的大规模数据和不须要被打断的纯净的计算环境。因此CPU擅长逻辑控制,串行的运算。和通用类型数据运算不一样,GPU擅长的是大规模并发计算,这也正是密码破解等所须要的。因此GPU除了图像处理,也愈来愈多的参与到计算当中来。参考
iOS中GPU在显示方面的工做主要是:接收提交的纹理(Texture)和顶点描述(三角形),进行变换(transform)、混合并渲染,而后输出到屏幕上。屏幕上的内容,主要也就是纹理(图片)和形状(三角模拟的矢量图形)两类。通常来讲,CALayer的大多数属性都是使用GPU来绘制的。虽然GPU在处理图像等渲染是速度很快,但若是开发过程当中使用不当,仍会致使GPU占用太高,渲染速度跟不上屏幕刷新致使卡顿。
全部的 Bitmap,包括图片、文本、栅格化的内容,最终都要由内存提交到显存,绑定为 GPU Texture。不管是提交到显存的过程,仍是 GPU 调整和渲染 Texture 的过程,都要消耗很多 GPU 资源。当在较短期显示大量图片时(好比 TableView 存在很是多的图片而且快速滑动时),CPU 占用率很低,GPU 占用很是高,界面仍然会掉帧。避免这种状况的方法只能是尽可能减小在短期内大量图片的显示,尽量将多张图片合成为一张进行显示。
当图片过大,超过 GPU 的最大纹理尺寸时,图片须要先由 CPU 进行预处理,这对 CPU 和 GPU 都会带来额外的资源消耗。目前来讲,iPhone 4S 以上机型,纹理尺寸上限都是 4096x4096,更详细的资料能够看这里:iosres.com。因此,尽可能不要让图片和视图的大小超过这个值。
当多个视图(或者说 CALayer)重叠在一块儿显示时,GPU 会首先把他们混合到一块儿。若是视图结构过于复杂,混合的过程也会消耗不少 GPU 资源。为了减轻这种状况的 GPU 消耗,应用应当尽可能减小视图数量和层次,并在不透明的视图里标明 opaque 属性以免无用的 Alpha 通道合成。固然,这也能够用上面的方法,把多个视图预先渲染为一张图片来显示。
CALayer 的 border、圆角、阴影、遮罩(mask),CASharpLayer 的矢量图形显示,一般会触发离屏渲染(offscreen rendering),而离屏渲染一般发生在 GPU 中。当一个列表视图中出现大量圆角的 CALayer,而且快速滑动时,能够观察到 GPU 资源已经占满,而 CPU 资源消耗不多。这时界面仍然能正常滑动,但平均帧数会降到很低。为了不这种状况,能够尝试开启 CALayer.shouldRasterize 属性,但这会把本来离屏渲染的操做转嫁到 CPU 上去。对于只须要圆角的某些场合,也能够用一张已经绘制好的圆角图片覆盖到本来视图上面来模拟相同的视觉效果。最完全的解决办法,就是把须要显示的图形在后台线程绘制为图片,避免使用圆角、阴影、遮罩等属性。
防止离屏渲染 OpenGL 中,GPU 屏幕渲染有如下两种方式:
相比于当前屏幕渲染,离屏渲染的代价是很高的,主要体如今两个方面: * **建立新缓冲区** 要想进行离屏渲染,首先要建立一个新的缓冲区。 * **上下文切换** 离屏渲染的整个过程,须要屡次切换上下文环境:先是从当前屏幕(On-Screen)切换到离屏(Off-Screen);等到离屏渲染结束之后,将离屏缓冲区的渲染结果显示到屏幕上有须要将上下文环境从离屏切换到当前屏幕。而上下文环境的切换是要付出很大代价的。 因此在图形生成的步骤咱们要尽量的避免离屏渲染
iOS开发中,在GPU优化上,咱们通常使用instruments中的Core Animation工具来进行滑动流畅度优化,在Core Animation中咱们可也看到列表滑动过程当中的FPS,其中有一些颇有用的debug选项,帮助咱们找到代码中有性能问题的代码。下面是一些经常使用的选项:
Color Blended Layers
Color Blended Layers是用来检测个半透明图层的混合区,渲染程度对屏幕中的混合区域进行绿到红的高亮。由于计算混合区的颜色时,致使overdraw,消耗必定的GPU资源,是致使滑动性能的一个因素。因此尽可能要尽可能避免
在开发过程当中,避免Blended Layers的手段有:
大多数时,Core Animation只须要提交原始图片的指针到render server,不涉及内存copy。可是一些状况下,Core Animation不得不copy一份图片发送到render server。苹果的GPU只解析32bit的颜色格式,若是图片颜色格式不对,CPU会预先格式转换。copy images是很是耗CPU的操做,必定要避免。
Color Offscreen-Rendered Yellow
GPU在当前屏幕缓冲区外开辟新的缓冲区进行渲染, 屏幕外缓冲区和当前屏幕缓冲区上下文切换是十分耗时的操做
引发Offscreen-Rendered的操做有:
- 圆角 cornerRadius masksToBounds同时设置 - 设置shadow - 开启光栅化 shouldRasterize=YES.CALayer 有一个 shouldRasterize 属性,将这个属性设置成 true 后就开启了光栅化。开启光栅化后会将图层绘制到一个屏幕外的图像,而后这个图像将会被缓存起来并绘制到实际图层的 contents 和子图层,对于有不少的子图层或者有复杂的效果应用,这样作就会比重绘全部事务的全部帧来更加高效。可是光栅化原始图像须要时间,并且会消耗额外的内存。光栅化也会带来必定的性能损耗,是否要开启就要根据实际的使用场景了,图层内容频繁变化时不建议使用。最好仍是用 Instruments 比对开启先后的 FPS 来看是否起到了优化效果。
避免Offscreen-Rendered的方式能够其余方式实现圆角、shadow + shadowPath等。 ## 总结 本文的讲了一些形成卡顿的缘由,以及CPU和GPU优化的经常使用技巧和工具,你们在优化的时候能够做为参考。但不要把优化手段局限在这些方面,不一样的应用有各自不一样的特色,必定要具体问题具体分析。甚至能够跳出技术范畴,在交互方面作一些文章,好比在减小列表每次从服务器获取的数据数量、采用用户手动点击触发获取更多数据而不是滑动过程当中自动获取、使用交互动画等均可以极大改善用户的滑动体验。 最后仍是要强调一下我上一篇文章讲的优化时候须要注意的几大原则,这样才能在优化过程当中有更好的全局观,尽可能少走弯路,但愿你们可以在优化过程当中时刻牢记。