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任意一个没能在时钟周期内完成自己的任务的话都会导致卡顿掉帧。

卡顿图示

行之有效的优化点

提前布局

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

cellLayout示例图

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

按页加载缓存

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

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

直观图示:

按页缓存

可以看到,优化之前整个新闻列表以及其他配置都在一个文件里,刷新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中UITableView的性能优化
  • IOS 性能优化中离屏渲染
  • iOS开发中UITableview控件的基本使用及性能优化方法
  • 详解优化iOS程序性能的25个方法
  • MySQL性能监控软件Nagios的安装及配置教程
(0)

相关推荐

  • IOS 性能优化中离屏渲染

    GPU屏幕渲染有以下两种方式: On-Screen Rendering 意为当前屏幕渲染,指的是GPU的渲染操作是在当前用于显示的屏幕缓冲区中进行. Off-Screen Rendering 意为离屏渲染,指的是GPU在当前屏幕缓冲区以外新开辟一个缓冲区进行渲染操作. 特殊的离屏渲染: 如果将不在GPU的当前屏幕缓冲区中进行的渲染都称为离屏渲染,那么就还有另一种特殊的"离屏渲染"方式: CPU渲染. 如果我们重写了drawRect方法,并且使用任何Core Graphics的技术进行了

  • 详解优化iOS程序性能的25个方法

    1. 用ARC管理内存 ARC(Automatic ReferenceCounting, 自动引用计数)和iOS5一起发布,它避免了最常见的也就是经常是由于我们忘记释放内存所造成的内存泄露.它自动为你管理retain和release的过程,所以你就不必去手动干预了.忘掉代码段结尾的release简直像记得吃饭一样简单.而ARC会自动在底层为你做这些工作.除了帮你避免内存泄露,ARC还可以帮你提高性能,它能保证释放掉不再需要的对象的内存. 现在所有的iOS程序都用ARC了,这条可以忽略. 2. 在

  • iOS开发中UITableview控件的基本使用及性能优化方法

    UITableview控件基本使用 一.一个简单的英雄展示程序 NJHero.h文件代码(字典转模型) 复制代码 代码如下: #import <Foundation/Foundation.h> @interface NJHero : NSObject /**  *  头像  */ @property (nonatomic, copy) NSString *icon; /**  *  名称  */ @property (nonatomic, copy) NSString *name; /**  

  • MySQL性能监控软件Nagios的安装及配置教程

    Nagios是一款Linux上成熟的监视系统运行状态和网络信息的开原IT基础设施监视系统,Nagios能监视所指定的本地或远程主机及服务,例如HTTP服务.FTP服务等,同时提供异常通知.事件处理等功能,当主机或服务出现故障时,Nagios还可以通过邮件.手机短信等形式在第一时间进行通知.Nagios可运行在Linux和Unix平台上,同时提供一个可选的基于浏览器的Web界面,方便系统管理员查看系统的运行状态.网络状态.各种系统问题及日志异常等. 环境: 192.168.0.201      m

  • 详细整理iOS中UITableView的性能优化

    一.介绍 iOS开发中,UITableView可能是平时我们打交道最多的UI控件之一,其重要性不言而喻.Android也是如此,Android中的ListView和UITableView是相同功能的一个控件,但是iOS的UITableView更为强大一点,原因就不说了,如果你学过Android就知道iOS中的UITableView使用起来是非常简单的,这也是峰哥喜欢iOS胜过Android的原因之一.今天研究的内容就是UITableView的优化. 开始之前,你能说出几种UITableView的

  • iOS性能优化浅析

    本文将从原理出发,解释卡顿发生的原理,然后会讲解项目中行之有效的几个优化点,最后会展望一下接下来将要尝试的方向.下面进入正题. 屏幕显示的原理 屏幕显示原理 我们知道,远古时代的CRT显示器的显示原理是用电子枪扫描荧光屏来发光.如上图所示,电子枪按照从左到右,然后从上到下的顺序扫描.当电子枪换到新的一行准备进行扫描时,也就是上图A4.B4.C4.D4的位置,显示器会发出一个水平同步信号:而当一帧画面绘制完成后,电子枪回复到原位准备画下一帧前,也就是上图D4的位置,显示器会发出一个垂直同步信号.垂

  • iOS性能优化教程之页面加载速率详解

    前言 我认为在编码过程中时刻注意性能影响是有必要的,但凡事都有个度,不能为了性能耽误了开发进度.在时间紧急的情况下我们往往采用"quick and dirty"的方案来快速出成果,后面再迭代优化,即所谓的敏捷开发.与之相对应的是传统软件开发中的瀑布流开发流程. 卡顿产生的原因 在 iOS 系统中,图像内容展示到屏幕的过程需要 CPU 和 GPU 共同参与.CPU 负责计算显示内容,比如视图的创建.布局计算.图片解码.文本绘制等.随后 CPU 会将计算好的内容提交到 GPU 去,由 GP

  • iOS开发教程之常见的性能优化技巧

    前言 性能问题的主要原因是什么,原因有相同的,也有不同的,但归根到底,不外乎内存使用.代码效率.合适的策略逻辑.代码质量.安装包体积这一类问题. 但从用户体验的角度去思考,当我们置身处地得把自己当做用户去玩一款应用时候,那么都会在意什么呢?假如正在玩一款手游,首先一定不希望玩着玩着突然闪退,然后就是不希望卡顿,其次就是耗电和耗流量不希望太严重,最后就是安装包希望能小一点.简单归类如下: 快:使用时避免出现卡顿,响应速度快,减少用户等待的时间,满足用户期望. 稳:不要在用户使用过程中崩溃和无响应.

  • iOS程序性能优化的技巧

    1. 用ARC管理内存 ARC(Automatic ReferenceCounting, 自动引用计数)和iOS5一起发布,它避免了最常见的也就是经常是由于我们忘记释放内存所造成的内存泄露.它自动为你管理retain和release的过程,所以你就不必去手动干预了.忘掉代码段结尾的release简直像记得吃饭一样简单.而ARC会自动在底层为你做这些工作.除了帮你避免内存泄露,ARC还可以帮你提高性能,它能保证释放掉不再需要的对象的内存. 2.尽量把views设置为透明 如果你有透明的Views你

  • iOS 下的图片处理与性能优化详解

    图片在计算机世界中怎样被存储和表示? 图片和其他所有资源一样,在内存中本质上都是0和1的二进制数据,计算机需要将这些原始内容渲染成人眼能观察的图片,反过来,也需要将图片以合适的形式保存在存储器或者在网络上传送. 这种将图片以某种规则进行二进制编码的方式,就是图片的格式. 常见的图片格式 图片的格式有很多种,除了我们熟知的 JPG.PNG.GIF,还有Webp,BMP,TIFF,CDR 等等几十种,用于不同的场景或平台. 这些格式可以分为两大类:有损压缩和无损压缩. 有损压缩:相较于颜色,人眼对光

  • 浅析Mysql Join语法以及性能优化

    一.Join语法概述 join 用于多表中字段之间的联系,语法如下: 复制代码 代码如下: ... FROM table1 INNER|LEFT|RIGHT JOIN table2 ON conditiona table1:左表:table2:右表. JOIN 按照功能大致分为如下三类: INNER JOIN(内连接,或等值连接):取得两个表中存在连接匹配关系的记录. LEFT JOIN(左连接):取得左表(table1)完全记录,即是右表(table2)并无对应匹配记录. RIGHT JOIN

  • 浅析安卓(Android)的性能优化

    Android性能的优化主要分为两点 1.布局优化 2.内存优化 布局优化 首先来看一下布局优化,系统在渲染UI的时候会消耗大量的资源,所以,对布局的优化就显得尤为重要 避免Overdraw 也就是避免过度的绘制,过度的绘制会浪费更多的资源,举个例子,Android系统会默认绘制Activity的背景,这时候我们再设置一个背景,这样默认的背景就属于过度绘制了,在『开发者工具』中有一个『调试GPU过度绘制』的选项,我们打开就可以通过颜色来判断过度绘制的次数 如图: 所以说我们尽可能的增大蓝色区域,

  • 浅析Mongodb性能优化的相关问题

    前言 如何能让软件拥有更高的性能?我想这是一个大部分开发者都思考过的问题.性能往往决定了一个软件的质量,如果你开发的是一个互联网产品,那么你的产品性能将更加受到考验,因为你面对的是广大的互联网用户,他们可不是那么有耐心的.严重点说,页面的加载速度每增加一秒也许都会使你失去一部分用户,也就是说,加载速度和用户量是成反比的.那么用户能够接受的加载速度到底是多少呢? 如图,如果页面加载时间超过10s那么用户就会离开,如果1s–10s的话就需要有提示,但如果我们的页面没有提示的话需要多快的加载速度呢?是

随机推荐