终端开发,首当其冲的就是视图、动画、切换等的渲染。用户在使用APP时最直接的感受就是界面是否好看,动画是否炫目,滑动流畅不流畅。UI是App的门面,它的体验伴随着用户使用App的全过程。如果UI失效,用户将没有第二次打开它的欲望。iOS为开发者提供了丰富的Framework(UIKit、CoreAnimation、CoreGraphic、OpenGL等)来满足从上层到下层的各种开发需求。不得不说苹果很强大。您无需了解许多接口背后的原理即可上手并满足您的大部分需求。但是,如果遇到性能问题,就很容易失明。易用性和优化是矛盾的,就像ARC一样,当你不遇到内存问题时,就可以很好地使用它。一旦遇到,你需要比使用MRC时更了解iOS的内存机制。用户界面也是如此。而且,作为鹅厂的员工,当然不能仅限于会用。我们需要知道为什么以及为什么。好了,废话不多说,进入正题:来看看iOS是如何渲染视图和动画的,以及当我们遇到渲染性能问题时如何优化。(注:以下内容是笔者的一些踩坑经验和总结,欢迎讨论!)首先我们看一下官方对CoreAnimation的描述:可以看出iOS渲染视图的核心是核心动画。从底层到上层依次是GPU->(OpenGL,CoreGraphic)->CoreAnimation->UIKit。在iOS上,动画和视图渲染实际上是在另一个进程中完成的(以下我们称这个进程为renderserver)。在iOS5之前,这个进程被称为SpringBoard,在iOS6之后,被称为BackBoard。下图是使用项目录制视频时整个系统的进度(大量视图渲染):可以清楚的看到BackBoard的进度。iOS上视图或动画渲染的各个阶段:APP内部有4个阶段:Layout:在这个阶段,程序设置View/Layer的层信息,设置层的属性,如frame,背景颜色,ETC。创建背景图:在这个阶段,程序会创建图层的背景图,无论是通过setContents传递图像给图层,还是通过drawRect:或drawLayer:inContext:来绘制。所以drawRect:等函数在这个阶段被调用。准备:在这个阶段,CoreAnimation框架准备好要渲染的层的各种属性数据,以及要传递给渲染服务器的动画参数。同时,要渲染的图像也在这个阶段解压。(除了使用imageNamed:方法从bundle中加载的图片会立即解压,其他图片如直接从硬盘读取或从网络下载的图片不会立即解压,只有在真正需要时才会解压)渲染)。提交:在这个阶段,CoreAnimation将图层信息和要做的动画的参数打包,通过IPC(进程间通信)传输给渲染服务器。APP外的两个阶段:当数据到达渲染服务器时,会被反序列化成渲染树。然后渲染服务器会做下面两件事:根据图层的各种属性(如果是动画,会计算动画图层属性的中间值),准备用OpenGL渲染。将这些可视层渲染到屏幕上。如果是动画,则重复最后两个步骤,直到动画结束。我们都知道iOS设备的屏幕刷新率为60HZ。如果以上步骤不能在一个刷新周期(1/60s)内完成,就会造成掉帧。让我们看看哪些操作可能会消耗过多的CPU或GPU,从而导致丢帧。视图上的图层或几何图形过多:如果视图层次结构过于复杂,则在渲染某些视图或修改框架时,CPU将花费更多时间重新计算框架。特别是如果使用autolayout,会消耗更多的CPU。同时过多的几何结构会大大增加需要渲染的OpenGLl三角形和光栅化操作(将OpenGL三角形转化为像素)。透支过多:透支是指一个像素多次被颜色填充。这主要是由于一些半透明层相互重叠。填充率(像素填充颜色的速率)受GPU限制。如果透支过多,势必会降低GPU的性能。视图的延迟加载:iOS只在显示viewcontroller的视图或访问viewcontroller的视图时才加载视图,比如someviewcontroller.view。如果用户点击一个按钮,在按钮的响应函数中做了很多耗cpu的工作,如果此时呈现一个viewcontroller,就很容易卡住,尤其是viewcontroller需要从数据库中获取数据,或从nib文件初始化视图或加载图像会更慢。离屏绘制:离屏绘制有两种情况:1.有些效果(如圆角、图层蒙版、阴影、图层光栅化)不能直接在屏幕上绘制,必须在离屏图像上绘制首先,此操作会引入额外的内存和CPU消耗。2.实现drawRect或drawLayer:inContext:,为了支持任意绘制,coregraphic会创建一个与要绘制的view大小相同的backingimage。并且当绘制完成后,会传输到渲染服务器进行渲染。所以不重载drawRect等函数什么也不做也无妨。图片解压:使用imageNamed:从bundle中加载会立即解压。通常,分配给UIImageView或绘制到核心图形上下文中的图像或图层内容将被解压缩。渲染性能优化注意事项:隐藏绘图:cattextlayer和uilabel都将文本绘制到backingimage中。如果更改包含文本的视图的框架,则将重新绘制文本。Rasterize:当使用图层的shouldRasterize(记得设置合适的图层r的rasterizationScale)时,图层将被强制绘制在离屏图像上并被缓存。该方法可用于缓存绘制耗时(例如效果更华丽)但不经常变化的图层。如果层变化频繁,则不适合使用。离屏绘图:使用圆角、图层蒙版、阴影效果可以使用可拉伸图像。例如,要实现圆角,您可以将圆形图像分配给图层的内容属性。并设置contentsCenter和contentScale属性。BlendingandOverdraw:如果一个图层被另一个图层完全覆盖,GPU会优化而不渲染被覆盖的图层,但是计算一个图层是否被另一个图层完全覆盖是非常耗费CPU的。将几个半透明层的颜色混合在一起也很昂贵。我们所做的:将视图的backgroundColor设置为固定的不透明颜色。如果视图是不透明的,请将不透明属性设置为YES。(直接告诉程序这是不透明的,而不是让程序去计算)这样会减少混合和透支。如果您使用图像,请尽量避免将图像的alpha设置为透明。如果有些效果需要多张图片融合,就让设计使用一张图片,不要让程序在运行时动态融合。好了,介绍完这些渲染优化中需要注意的点,下面我们用仪器的CoreAnimation和GPU驱动来看一些具体的例子。CoreAnimation:ColorBlendedLayers:查看半透明层的覆盖范围。从绿色到红色,越红覆盖范围越大。这两张图是测量项目详情页半透明层的情况。可以看到详情页上还是有很多半透明层,但是不代表有很多半透明层。如果范围很大,则需要对其进行优化。请参考GPU驱动的实测情况。下面将介绍ColorHitsGreenandMissesRed:当使用shouldRasterize时,图层绘制会被缓存,如果光栅化图层需要重绘,则会被标记为红色。下面是在全民K歌新闻榜的cell中设置shouldRasterize=YES的情况。每个单元格图层的背景图像将被缓存。如果向下滚动表格视图,单元格将被重新使用,因此图层将被重绘,将被标记为红色。ColorOffscreen-RenderedYellow:如果要进行离屏绘制,会被标记为黄色。以下是该项目的主页。由于圆形头像是通过设置roundedCorner属性实现的,所以会触发离屏绘制。CoreAnimation模板只是让开发者直观的看到哪些地方可能需要优化,而优化与否取决于GPU驱动的性能。GPUDriverRendererUtilization-如果该值大于50%,则表示GPU的性能受到fill-rate的限制,可能存在过多的Offscreen渲染、透支、混合。TilerUtilization-如果此值大于50%,则图层可能过多。我们以上面项目的详情页为例,查看GPU驱动的实测:可以看到RendererUtilization在20%左右,TilerUtilization在15%左右,所以不需要优化它。(当然这里我们不考虑CPU占用率,这里只是针对上面核心动画模板的一些测量,不需要针对性的优化)这里我想说的是,以上几点只是在我们遇到渲染性能问题时,为我们提供优化方向和思路。优化往往代表更复杂、更难理解的代码,所以在没有遇到渲染性能问题的情况下,不要过度优化。希望对大家有所帮助!
