iOS 性能优化总结

卡顿产生的缘由

VSync 信号到来后,系统图形服务会经过 CADisplayLink 等机制通知 AppApp 主线程开始在 CPU 中计算显示内容,好比视图的建立、布局计算、图片解码、文本绘制等。随后 CPU 会将计算好的内容提交到 GPU 去,由 GPU 进行变换、合成、渲染。随后 GPU 会把渲染结果提交到帧缓冲区去,等待下一次 VSync 信号到来时显示到屏幕上。因为垂直同步的机制,若是在一个 VSync 时间内,CPU 或者 GPU 没有完成内容提交,则那一帧就会被丢弃,等待下一次机会再显示,而这时显示屏会保留以前的内容不变。这就是界面卡顿的缘由。ios

在开发中,CPUGPU中任何一个压力过大,都会致使掉帧现象,因此在开发时,也须要分别对CPUGPU压力进行评估和优化。git

iOS 设备中的 CPU & GPU

CPU

加载资源,对象建立,对象调整,对象销毁,布局计算,Autolayout,文本计算,文本渲染,图片的解码, 图像的绘制(Core Graphics)都是在CPU上面进行的。github

GPU

GPU是一个专门为图形高并发计算而量身定作的处理单元,比CPU使用更少的电来完成工做而且GPU的浮点计算能力要超出CPU不少。算法

GPU的渲染性能要比CPU高效不少,同时对系统的负载和消耗也更低一些,因此在开发中,咱们应该尽可能让CPU负责主线程的UI调动,把图形显示相关的工做交给GPU来处理,当涉及到光栅化等一些工做时,CPU也会参与进来,这点在后面再详细描述。缓存

相对于CPU来讲,GPU能干的事情比较单一:接收提交的纹理(Texture)和顶点描述(三角形),应用变换(transform)、混合(合成)并渲染,而后输出到屏幕上。一般你所能看到的内容,主要也就是纹理(图片)和形状(三角模拟的矢量图形)两类。性能优化

CPU 和 GPU 的协做

由上图可知,要在屏幕上显示视图,须要CPUGPU一块儿协做,CPU计算好显示的内容提交到GPUGPU渲染完成后将结果放到帧缓存区,随后视频控制器会按照 VSync 信号逐行读取帧缓冲区的数据,通过可能的数模转换传递给显示器显示。session

缓冲机制

iOS使用的是双缓冲机制。即GPU会预先渲染好一帧放入一个缓冲区内(前帧缓存),让视频控制器读取,当下一帧渲染好后,GPU会直接把视频控制器的指针指向第二个缓冲器(后帧缓存)。当你视频控制器已经读完一帧,准备读下一帧的时候,GPU会等待显示器的VSync信号发出后,前帧缓存和后帧缓存会瞬间切换,后帧缓存会变成新的前帧缓存,同时旧的前帧缓存会变成新的后帧缓存。并发

优化方案

YY大神的 iOS 保持界面流畅的技巧中详细介绍了 CPU 资源消耗缘由和解决方案GPU 资源消耗缘由和解决方案,这里面包括了开发中的大部分场景,能够帮助咱们快速定位卡顿的缘由,迅速解决卡顿。框架

下面是一些常见的优化方案!异步

TableViewCell 复用

cellForRowAtIndexPath:回调的时候只建立实例,快速返回cell,不绑定数据。在willDisplayCell: forRowAtIndexPath:的时候绑定数据(赋值)。

高度缓存

tableView滑动时,会不断调用heightForRowAtIndexPath:,当 cell 高度须要自适应时,每次回调都要计算高度,会致使 UI 卡顿。为了不重复无心义的计算,须要缓存高度。

怎么缓存?

视图层级优化

不要动态建立视图
  • 在内存可控的前提下,缓存subview
  • 善用hidden
减小视图层级
  • 减小subviews个数,用layer绘制元素。
  • 少用 clearColormaskToBounds,阴影效果等。
减小多余的绘制操做

图片

  • 不要用JPEG的图片,应当使用PNG图片。
  • 子线程预解码(Decode),主线程直接渲染。由于当image没有Decode,直接赋值给imageView会进行一个Decode操做。
  • 优化图片大小,尽可能不要动态缩放(contentMode)。
  • 尽量将多张图片合成为一张进行显示。

减小透明 view

使用透明view会引发blending,在iOS的图形处理中,blending主要指的是混合像素颜色的计算。最直观的例子就是,咱们把两个图层叠加在一块儿,若是第一个图层的透明的,则最终像素的颜色计算须要将第二个图层也考虑进来。这一过程即为Blending

会致使blending的缘由:

  • UIViewalpha < 1
  • UIImageViewimage含有alpha channel(即便UIImageViewalpha1,但只要image含有透明通道,则仍会致使blending)。

为何blending会致使性能的损失?

缘由是很直观的,若是一个图层是不透明的,则系统直接显示该图层的颜色便可。而若是图层是透明的,则会引发更多的计算,由于须要把另外一个的图层也包括进来,进行混合后的颜色计算。

  • opaque设置为YES,减小性能消耗,由于GPU将不会作任何合成,而是简单从这个层拷贝。

减小离屏渲染

离屏渲染指的是在图像在绘制到当前屏幕前,须要先进行一次渲染,以后才绘制到当前屏幕。

OpenGL中,GPU屏幕渲染有如下两种方式:

  • On-Screen Rendering即当前屏幕渲染,指的是GPU的渲染操做是在当前用于显示的屏幕缓冲区中进行。

  • Off-Screen Rendering即离屏渲染,指的是GPU在当前屏幕缓冲区之外新开辟一个缓冲区进行渲染操做。

为何离屏渲染会发生卡顿?主要包括两方面内容:

  • 建立新的缓冲区。
  • 上下文切换,离屏渲染的整个过程,须要屡次切换上下文环境(CPU渲染和GPU切换),先是从当前屏幕(On-Screen)切换到离屏(Off-Screen);等到离屏渲染结束之后,将离屏缓冲区的渲染结果显示到屏幕上又须要将上下文环境从离屏切换到当前屏幕。而上下文环境的切换是要付出很大代价的。

设置了如下属性时,都会触发离屏渲染:

  • layer.shouldRasterize,光栅化

  • layer.mask,遮罩

  • layer.allowsGroupOpacityYESlayer.opacity的值小于1.0

  • layer.cornerRadius,而且设置layer.masksToBoundsYES。可使用剪切过的图片,或者使用layer画来解决。

  • layer.shadows,(表示相关的shadow开头的属性),使用shadowPath代替。

    两种不一样方式来绘制阴影: 不使用shadowPath

    使用shadowPath

    性能差异,以下图:

离屏渲染的优化建议

  • 使用ShadowPath指定layer阴影效果路径。
  • 使用异步进行layer渲染(Facebook开源的异步绘制框架AsyncDisplayKit)。
  • 设置layeropaque值为YES,减小复杂图层合成。
  • 尽可能使用不包含透明(alpha)通道的图片资源。
  • 尽可能设置layer的大小值为整形值。
  • 直接让美工把图片切成圆角进行显示,这是效率最高的一种方案。
  • 不少状况下用户上传图片进行显示,能够在客户端处理圆角。
  • 使用代码手动生成圆角image设置到要显示的View上,利用UIBezierPathCore Graphics框架)画出来圆角图片。

合理使用光栅化 shouldRasterize

光栅化是把GPU的操做转到CPU上,生成位图缓存,直接读取复用。

优势:
  • CALayer会被光栅化为bitmapshadowscornerRadius等效果会被缓存。
缺点:
  • 更新已经光栅化的layer,会形成离屏渲染。
  • bitmap超过100ms没有使用就会移除。
  • 受系统限制,缓存的大小为 2.5X Screen Size。

shouldRasterize 适合静态页面显示,动态页面会增长开销。若是设置了shouldRasterizeYES,那也要记住设置rasterizationScalecontentsScale

异步渲染

在子线程绘制,主线程渲染。例如 VVeboTableViewDemo

理性使用-drawRect:

你们或许感到奇怪,有很多开发者在发有关性能优化的博客当中指出使用-drawRect:来优化性能。可是我这里不太建议你们未经思考的使用-drawRect:方法。缘由以下:

当你使用UIImageView在加载一个视图的时候,这个视图虽然依然有CALayer,可是却没有申请到一个后备的存储,取而代之的是使用一个使用屏幕外渲染,将CGImageRef做为内容,并用渲染服务将图片数据绘制到帧的缓冲区,就是显示到屏幕上,当咱们滚动视图的时候,这个视图将会从新加载,浪费性能。因此对于使用-drawRect:方法,更倾向于使用CALayer来绘制图层。由于使用CALayer-drawInContext:Core Animation将会为这个图层申请一个后备存储,用来保存那些方法绘制进来的位图。那些方法内的代码将会运行在 CPU上,结果将会被上传到GPU。这样作的性能更为好些。

静态界面建议使用-drawRect:的方式,动态页面不建议。

按需加载

  • 局部刷新,刷新一个cell就能解决的,坚定不刷新整个 section 或者整个tableView刷新最小单元元素
  • 利用runloop提升滑动流畅性,在滑动中止的时候再加载内容,像那种一闪而过的(快速滑动),就没有必要加载,可使用默认的占位符填充内容。

关于性能测试

在出现图像性能问题,滑动,动画不够流畅以后,咱们首先要作的就是定位出问题的所在。而这个过程并非只靠经验和穷举法探索,咱们应该用有脉络,有顺序的科学的手段进行探索。

首先,咱们要有一个定位问题的模式。咱们能够按照这样的顺序来逐步定位,发现问题。

  1. 定位帧率,为了给用户流畅的感觉,咱们须要保持帧率在60帧左右。当遇到问题后,咱们首先检查一下帧率是否保持在60帧。
  2. 定位瓶颈,到底是CPU仍是GPU。咱们但愿占用率越少越好,一是为了流畅性,二也节省了电力。
  3. 检查有没有作无必要的CPU渲染,例若有些地方咱们重写了drawRect:,而实际上是咱们不须要也不该该的。咱们但愿GPU负责更多的工做。
  4. 检查有没有过多的离屏渲染,这会耗费GPU的资源,像前面已经分析的到的。离屏渲染会致使GPU须要不断地onScreenoffscreen进行上下文切换。咱们但愿有更少的离屏渲染。
  5. 检查咱们有无过多的BlendingGPU渲染一个不透明的图层更省资源。
  6. 检查图片的格式是否为经常使用格式,大小是否正常。若是一个图片格式不被GPU所支持,则只能经过CPU来渲染。通常咱们在iOS开发中都应该用PNG格式,以前阅读过的一些资料也有指出苹果特地为PNG格式作了渲染和压缩算法上的优化。
  7. 检查是否有耗费资源多的View或效果,咱们须要合理有节制的使用。
  8. 最后,咱们须要检查在咱们View层级中是否有不正确的地方。例若有时咱们不断的添加或移除View,有时就会在不经意间致使bug的发生。
测试工具:
  • Core AnimationInstruments里的图形性能问题的测试工具。
  • view debugging,Xcode 自带的,视图层级。
  • reveal,视图层级。

参考文章

若有内容错误,欢迎 issue 指正。

转载请注明出处!