iOS性能优化探讨

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

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

屏幕显示的原理

基本原理

屏幕显示原理

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

计算机工作原理

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

屏幕撕裂(Screen tearing)

上述的简单的屏幕显示原理其实会产生这样一个问题:假设我们的显卡速度很快,每秒生产的帧数肯定要超过显示器刷新率。那么在实际数据处理过程中,缓冲区的数据,在被输出之前,就被显卡不断的刷新重写。但是缓冲区并不是“先清空再写入数据”,这太没有效率,而是采用“新数据覆盖老数据”的方式。
假设这样一种情况,缓冲区已经有一副完整的帧画面(A帧),然后显卡生成了下一帧画面(B帧),新一帧的数据开始写入缓冲区,写到一半的时候,垂直同步信号来 了,于是缓冲区的数据被输出到显示器。但问题是,这时缓冲区的数据,是由一半A帧和一半B帧数据合成的。因此最终显示器上显示出来的画面就不是一副完整的 画面,这就是“画面撕裂”现象出现的原因(如下图)。

屏幕撕裂

那怎么才能解决画面撕裂呢?简单来说只要让帧缓冲区里的数据始终保持一副完整的画面就可以了。从技术角度出发,其实就是利用刚刚提到的垂直同步信号。
具体说起来就是,当显卡生成了一副完整画面并写入了帧缓冲区之后,暂停!然后开始等待垂直同步信号,当得到垂直同步信号后,再继续渲染下一帧写入缓冲区。这样就可以保证在缓冲区的数据始终是一副完整的画面,不会出现前后帧混合的问题。

卡顿产生原因

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

卡顿图示

行之有效的优化点

  1. 提前布局

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

cellLayout示例图

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

  1. 按页加载缓存

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

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

  • 直观图示:

按页缓存

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

  1. 后台线程处理图片

圆形头像、图片裁圆角等处理可以说是非常常见的需求了,包括从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];
               }
           });
       }];
  1. 更加高效的控件

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

  1. 其他

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

  • 尽量减少视图层级,合并多余的视图。同样以 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技术团队的公众号打个广告--小扎开发笔记,团队内的几位大牛会将多年开发经验总结成文。传递价值资讯,更多精彩内容,等你来发现~

以上。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,319评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,801评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,567评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,156评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,019评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,090评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,500评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,192评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,474评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,566评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,338评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,212评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,572评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,890评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,169评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,478评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,661评论 2 335

推荐阅读更多精彩内容

  • 本文将从原理探究一下iOS出现界面出现卡顿的原因,以及给出适当的解决办法 一.屏幕显示原理 最早的CRT显示器...
    SessionSinGod阅读 361评论 0 0
  • 卡顿优化 在了解卡顿优化相关的前头,首先要了解 CPU 和 GPU。 CPU(Central Processing...
    super_2e20阅读 740评论 0 8
  • iOS的性能优化主要可提现在以前的几个方面:卡顿优化、耗电优化、启动优化、安装包的瘦身。 1、卡顿优化 在了解卡顿...
    迷失的信徒阅读 406评论 0 1
  • 屏幕显示图像的原理 CRT 的电子枪按照上面方式,从上到下一行行扫描,扫描完成后显示器就呈现一帧画面,随后电子枪回...
    SnoopPanda阅读 632评论 1 5
  • iOS视图成像理论及优化 CRT屏幕成像 CRT(阴极射线管)显示器电子枪,电子枪从屏幕的左上角的第一行开始,从左...
    Jacky_Yang阅读 1,514评论 1 6