iOS性能优化探讨

点击上方“iOS开发”,选择“置顶公众号”数组

关键时刻,第一时间送达!缓存

640?

640?wx_fmt=gif


最近在公司内部作了一个分享会,探讨了iOS上性能优化的话题,如今将重点的内容整理好发出来,各位大牛斧正。
安全


本文将从原理出发,解释卡顿发生的原理,而后会讲解项目中行之有效的几个优化点,最后会展望一下接下来将要尝试的方向。下面进入正题。性能优化


屏幕显示的原理服务器


基本原理框架


640?wx_fmt=png

屏幕显示原理异步


咱们知道,远古时代的CRT显示器的显示原理是用电子枪扫描荧光屏来发光。如上图所示,电子枪按照从左到右,而后从上到下的顺序扫描。当电子枪换到新的一行准备进行扫描时,也就是上图A四、B四、C四、D4的位置,显示器会发出一个水平同步信号;而当一帧画面绘制完成后,电子枪回复到原位准备画下一帧前,也就是上图D4的位置,显示器会发出一个垂直同步信号。垂直同步信号的做用一方面是通知显示器回到A1位置,另一方面,也通知显卡,准备输出下一帧画面。如今已是液晶显示器的时代了,再也不使用电子枪扫描了,可是原理仍是相似的,水平同步信号和垂直同步信号仍是同样被使用的。async


640?wx_fmt=png

计算机工做原理函数


计算机系统的工做原理如上图所示:首先是CPU的工做,包括建立视图分配内存、计算布局、图片解码以及文本绘制等;接下来轮到GPU工做了,GPU负责视图变换、合成和渲染等;GPU渲染完提交到帧缓冲区中,等收到垂直同步信号后将帧缓冲区的内容显示到屏幕上。布局


屏幕撕裂(Screen tearing)


上述的简单的屏幕显示原理其实会产生这样一个问题:假设咱们的显卡速度很快,每秒生产的帧数确定要超过显示器刷新率。那么在实际数据处理过程当中,缓冲区的数据,在被输出以前,就被显卡不断的刷新重写。可是缓冲区并非“先清空再写入数据”,这太没有效率,而是采用“新数据覆盖老数据”的方式。

假设这样一种状况,缓冲区已经有一副完整的帧画面(A帧),而后显卡生成了下一帧画面(B帧),新一帧的数据开始写入缓冲区,写到一半的时候,垂直同步信号来 了,因而缓冲区的数据被输出到显示器。但问题是,这时缓冲区的数据,是由一半A帧和一半B帧数据合成的。所以最终显示器上显示出来的画面就不是一副完整的 画面,这就是“画面撕裂”现象出现的缘由(以下图)。


640?wx_fmt=jpeg

屏幕撕裂


那怎么才能解决画面撕裂呢?简单来讲只要让帧缓冲区里的数据始终保持一副完整的画面就能够了。从技术角度出发,其实就是利用刚刚提到的垂直同步信号。

具体提及来就是,当显卡生成了一副完整画面并写入了帧缓冲区以后,暂停!而后开始等待垂直同步信号,当获得垂直同步信号后,再继续渲染下一帧写入缓冲区。这样就能够保证在缓冲区的数据始终是一副完整的画面,不会出现先后帧混合的问题。


卡顿产生缘由


可是呢,垂直同步带来了一个新的问题—掉帧。所谓的掉帧,跟垂直同步有必定关系,由于垂直同步机制决定了若是在一个时钟周期内CPU或者GPU没有完成各自的任务的话,就会将帧缓冲区里的内容直接丢弃!掉帧并不能彻底怪罪于垂直同步机制,更重要的缘由是咱们做为开发者没有进行足够的优化,将太重的任务派发到了CPU或者GPU上,下图(from:iOS 保持界面流畅的技巧)是掉帧的图示,代表CPU或者GPU任意一个没能在时钟周期内完成本身的任务的话都会致使卡顿掉帧。


640?wx_fmt=png

卡顿图示


行之有效的优化点


提早布局


提早布局能够说是最重要的优化点了。其实在从服务端拿到 JSON 数据的时候,关于视图的布局就已经肯定了,包括每一个控件的frame、cell的高度以及文本排版结果等等,在这个时候彻底能够在后台线程计算并封装为对应的布局对象XXXTableViewCellLayout,每一个cellLayout的内存占用并非不少,因此直接所有缓存到内存中。当列表滚动到某个cell的时候,直接拿到对应的cellLayout配置这个cell的对应属性便可。固然,该有的计算是免不了的,只是提早算好并缓存,免去了在滚动的时候计算和重复的计算。经过这一个优化,将原本的fps50的列表优化到了5五、56左右,能够说从肉眼上已经看不出有卡顿掉帧了。


640?wx_fmt=jpeg

cellLayout示例图


上图是项目中某个cellLayout的部分代码,能够看到里面存的就是全部控件的frame和文本的排版结果而已,里面没有任何的黑科技,只是将原本在滚动中才作的事情提早了而已。


按页加载缓存


  • 现状分析:90%的APP有tableview,90%的tableview里有上拉刷新和下拉加载。以我司的项目ZAKER中的热点新闻界面为例,简单流程大概是这样的:①应用启动的时候会将磁盘中全部的新闻一次性读取出来显示到屏幕上; ②在每次下拉刷新和上拉加载的时候会将内存中全部新闻缓存到磁盘中,也即全量读写。这意味着大部分的新闻数据会反复写入到磁盘中,这样的写入是冗余的,由于前面的这些新闻数据并无发生改变。


  • 改进方案:因此优化的方法就是将这些列表数组进行分割,分割成一页一页,每次写入的数据量很小,并且避免了冗余写入的问题。如今的流程变为:①启动时只读取第一批新闻显示在屏幕中;②下拉刷新和上拉加载的时候只把当前服务器返回的一批新闻写入缓存中;③在上拉加载的时候会先查看磁盘中是否有未读的缓存,如有则读取缓存,不然才从服务器下载一批新的文章。


  • 直观图示:


640?wx_fmt=png

按页缓存


能够看到,优化以前整个新闻列表以及其余配置都在一个文件里,刷新10几回以后文件大小达到2MB,而且随着不断刷新而愈来愈大;优化以后,其余的配置仍是在刚刚的文件中,可是不断增加的新闻数组被分割成一页一页的文件,每一页里面有10多条的新闻数据,同时有一个configure文件保存这些页的信息以及页的顺序。根据测试人员的反馈,进行按页加载缓存优化能减小5%~8%的CPU占用,使用的内存也有必定的降低。仍是有很明显的优化效果的。


后台线程处理图片


圆形头像、图片裁圆角等处理能够说是很是常见的需求了,包括从iOS11的系统各处都能看到,总体的页面控件都变得更加圆润了。可是,对图片处理必然是消耗资源的,实现过图片圆角效果的应该都知道,最简单的就是 layer.cornerRadius+layer.masksToBounds 的方式,可是这种作法在tableview中每每会使滚动变得卡顿,由于这种实现方式会触发离屏渲染,屏幕外缓冲区跟当前屏幕缓冲区上下文切换是很耗性能的,因此离屏渲染每每会形成卡顿(参考:iOS 离屏渲染的研究)。

那要怎么处理图片呢?可使用Core Graphics,Core Graphic一般是线程安全的,因此能够进行异步绘制,显示的时候再放回主线程。咱们在项目中实现了一个后台处理图片的框架,核心代码以下:


NSBlockOperation *transformOperation = [[NSBlockOperation alloc] init];

       [transformOperation addExecutionBlock:^{        

           // 此到处理图片

           ...

           dispatch_async(dispatch_get_main_queue(), ^{

                    // 主线程设置图片

                   [self setImage:transformedImage forState:UIControlStateNormal];

               }

           });

       }];


更加高效的控件


还能够直接从开源库中选用更加高效的控件替换项目中性能没那么好的控件。项目中将以前的TTTAttributedLabel、M80AttributedLabel所有替换为YYLabel,开启YYLabel的displaysAsynchronously、ignoreCommonProperties属性能够异步绘制文本以及忽略不须要的属性。更加追求性能的话,能够结合第1点的提早布局机制,在提早布局的阶段生成好YYLabel渲染时用到的textLayout,显示的时候直接赋值textLayout就能够了。


其余


还有一些比较微小的优化,对性能能够说没有多大的影响,可是能够在开发阶段稍加留意,养成良好的习惯。


  • 尽可能减小视图层级,合并多余的视图。一样以 ZAKER 为例,用户显示时的蓝V标签、达人标签以及楼主图片等几个视图,以前是用不一样的view来展现的,优化过程将这几个view合并为一个view,一个view管理这些类似的事物,也能够减小某些相同逻辑的代码。


  • 减小频繁的addSubview、removeSubview,remove以后视图的实例对象会被释放,再add的时候会再次调用初始化函数。替代方案的话,能够用hidden属性隐藏不显示的视图。


接下来的方向


异步绘制


从一开始接触iOS的咱们就反复被告知,UIKit的东西是绝对不能在后台线程调用的,必定得在主线程调用,因此主线程也被叫作UI线程。在后台线程调用UIKit的东西有必定概率致使崩溃,或者出现视图不显示、显示错乱等等问题。可是呢,根据刚刚所说的,Core Graphics的那一套东西是线程安全的,因此能够经过Core Graphics在后台将视图渲染到一张图片上,显示的时候在主线程将这张图片设置到相应位置上。Facebook著名的AsyncDisplayKit的核心实现应该也是基于这个原理,接下来的优化能够尝试这个方案。


Metal


根据Apple官方说法,Metal框架被设计用来实现两个目标: 3D 图形渲染和并行计算。这二者有不少共同点。它们都在数量庞大的数据上并行运行特殊的代码,并能够在GPU上执行。目前正在研究学习阶段,看项目中是否能利用Metal进行必定的优化。


APM?


Application Performance Management(APM):应用程序性能管理, 经过对应用的可靠性、稳定性等方面的监控,进而达到能够快速修复问题、提升用户体验的目的。目前比较有表明性的 APM 产品有:听云、阿里百川、腾讯 bugly等,如今也在考虑本身研发一套APM系统,先从比较简单的指标入手,先对卡顿和崩溃这两个指标着手,作的顺利的话再逐步扩展别的指标的检测管理。


最后再悄悄咪咪的为我司iOS技术团队的公众号打个广告--小札开发笔记,团队内的几位大牛会将多年开发经验总结成文。传递价值资讯,更多精彩内容,等你来发现~


以上。


640?

  • 做者:又吉君的ruanpapa

  • https://www.jianshu.com/p/c83cb4eec901

  • iOS开发整理发布,转载请联系做者受权

640?wx_fmt=gif

640?【点击成为Android大神】