本篇主要记录一下我对界面优化、时间优化和耗电优化、安装包瘦身上的一些探索。我尽量按照自己的理解来进行描述,如有不当,欢迎指正。
一、界面优化
1、卡顿原理
要了解卡顿原理,需要对帧缓冲区、垂直同步、CPU 和 GPU 几个词进行一下了解,然后综合起来,就可以得到卡顿的答案。
1.1、帧缓冲区
听起来很高大上,其实就是用来存放每一帧画面数据的一个 “仓库”,一个仓库只存放一帧画面的数据,iOS 一直是双缓存,就是有两个仓库,存当前帧数据的叫 “正式仓库”,存下一帧数据的叫 “预备仓库”。
当正式仓库的数据被取走后,两者身份交换,原来的预备仓库转正为正式仓库,原来的正式仓库变成预备仓库。
1.2、垂直同步 (VSync)
就是一个“信号”,通知 APP 该开始准备往预备仓库里存放数据了,系统过一会就要来取,这个时间大概是 16.7 毫秒。
1.3、CPU (中央处理器)
主要的工作有:正式对象的创建和销毁、对象属性的调整、布局计算、文本的计算和排版、图片的格式转换和解码、图像的绘制。我们可以理解为负责包裹内部的处理工作,简称 “打包”。
1.4、GPU (图形处理器)
主要的工作有:将 CPU 计算好的内容进行变换、合成、渲染等处理,然后将渲染结果提交到帧缓冲区。我们可以理解为,对 CPU 给过来的包裹进行分类、排列等操作后,存放到仓库里去,简称 “入库”。
1.5、卡顿原理
当收到系统发过来的 VSync “信号”后,CPU 就开始对这一帧画面的数据进行 “打包”,然后交给 GPU 进行 “入库” 操作,存入到 “预备仓库” 中。
16.7 毫秒后,预备仓库转正,系统开始读取仓库里的数据,如果这个时候,仓库中的数据还没有准备好,那么系统就会大发雷霆,放弃读取仓库中的数据。
那么这个时候,因为系统的宁缺毋滥,导致了显示器上显示的还是上一帧画面,就造成了卡顿的效果。
2、优化
作为软件开发工程师的我们,既不能延长 16.7 毫秒的处理时间,也不能改变系统的脾气,那我们能做的就是尽量在这个时间内完成数据的准备。要么 “打包” 快一点,要么 “入库” 快一点,也就是针对 CPU 和 GPU 的工作进行优化,这就是性能优化的工作了。
2.1、CPU 工作之正式对象的创建和销毁
UITableViewCell 和 UICollectionViewCell 的复用,可以减少 cell 的创建操作;
尽量使用轻量级的对象,可以减少对象的创建时间,比如在不需要事件处理的场景里,使用CALayer 比 UIView 会更加合适;
表情键盘使用 UICollectionViewCell 代替 UIButton,可以减少对象的创建操作;
对象不涉及UI操作,放到后台线程创建;
性能敏感的界面,storyborad 的资源消耗>代码创建;
推迟对象创建的时间,对象放到多个任务中,比如懒加载;
2.2、CPU 工作之对象属性的调整
- UIView 有一个 CALyer 的属性,UIView 负责事件的处理,CALyer 负责图层的绘制和显示。需要注意的是,CALyer本身是没有属性的,所以当改变 UIView 的显示相关的属性如 frame、bounds 和 transform 的时候,会消耗较多的资源,所以减少对这些属性的一些不必要修改,能减小 CPU 的压力;
2.3、CPU 工作之布局计算
UITableViewCell 高度提前计算并存储,要用的时候直接读取;
frame 计算好,减少不必要的修改;
Autolayout 会比直接设置 frame 消耗更多的 CPU 资源;
2.4、CPU 工作之文本的计算和排版
普通文本可以在子线程用 [NSAttributedString boundingRectWithSize:options:context:] 来计算文本宽高,用 -[NSAttributedString drawWithRect:options:context:] 来绘制文本;
CoreText 对象占用内存较少,当显示大量文本时,可以用 CoreText 对文本异步绘制;
2.5、CPU 工作之图片的格式转换和解码
- 在后台线程先把图片绘制到 CGBitmapContext 中,然后从 Bitmap 直接创建图片;
2.6、CPU 工作之图像的绘制
UITableViewCell 滑动减速的时候才加载图片,可以参考这个demo;
加载图片时,imageNamed 方法默认加载图片成功后会内存中缓存图片,下次读取会很快;imageWithContentsOfFile 方法不会缓存图片,大图片可以使用该方法;
2.7、GPU 工作之渲染
尽量不要让图片和视图的大小超过 GPU 纹理尺寸上限:4096 × 4096,不然图片还需要经过 CPU 的处理;
尽量减少视图数量和层次,并设置视图为不透明,UIView 的不透明属性 (opaque) 默认为 YES,一般设置背景颜色即可;CALayer 的不透明属性 (opaque) 默认为 NO,需要设置为 YES;
CALayer 的 border、圆角、阴影、遮罩,通常会触发离屏渲染,尽量少用,圆角属性可以使用 CoreGraphics 绘制或使用圆角图片代替,关于离屏渲染的知识,具体可以参考这篇文章:iOS 图形性能优化;
图片的 size 最好刚好跟 UIImageView 的 size 保持一致,这涉及到像素对齐的知识,也可以在上面这篇文章中详细了解;
二、时间优化
要谈论时间优化,就要先了解程序启动的过程和耗时的原因,然后针对性的进行优化。
1、程序启动过程
程序的启动分为冷启动和热启动两种模式,其中冷启动是从程序被杀死后加载起来的过程,热启动是从后台到前台的过程。相比之下,热启动是包含在冷启动里,并且比冷启动少了部分加载过程的,所以,我们平常说的启动优化,一般都是针对冷启动的。
从点击程序的图标,到首页渲染完成显示到用户眼前,主要有三个阶段。
1.1、阶段一:main 函数之前
该阶段主要进行动态链接库 (dylib) 和自身 App 可执行文件的加载。
其中动态链接库包括:iOS 中用到的所有系统 framework,加载 OC runtime 方法的 libobjc,系统级别的 libSystem,例如 libdispatch(GCD) 和 libsystem_blocks (Block)。
1.2、阶段二:main 函数到首页加载之前
该阶段主要执行 main 函数到 applicationWillFinishLaunching 方法结束。
1.3、阶段三:首页开始加载到渲染完成
该阶段主要执行首页界面 viewDidLoad 方法和 UITabBarController 第一个子控制器 viewWillAppear 里的代码。
2、耗时产生原因
2.1、阶段一里可能产生耗时的有:
加载大量动态链接库;
注册大量 Objc 类 、初始化类对象 (Objc 的 +load 方法);
加载大量分类里的方法;
加载大量 C++ 静态对象;
执行大量声明为 attribute((constructor)) 的C函数。
2.2、阶段二里可能产生耗时的有:
在 applicationWillFinishLaunching 执行了 UITabBarController 以及 子控制器的创建,并在 viewDidLoad 方法里执行了大量的耗时操作;
大量第三方应用的配置和启动项的累积;
2.3、阶段三里可能产生耗时的有:
- 在 UITabBarController 第一个子控制器的 viewWillAppear 方法里执行了大量的耗时操作;
3、启动时间优化
3.1、针对阶段一的优化:
减少非系统库的依赖、合并非系统库,苹果最多支持6个非系统的动态库合并为一个;
定期清理项目里不使用的类和方法,检测工具可以使用AppCode,关于AppCode的使用请参考这篇文章: AppCode使用介绍;
将不必须在 +load 方法中做的事情延迟到 +initialize 中,关于二者的区别可以参考这篇文章;
减少分类和分类里方法的数量;
尽量不要用 C++ 虚函数;
删减一些无用的静态变量。
3.2、针对阶段二的优化:
对启动项和第三方应用配置根据优先级区分,部分不需要在程序启动就初始化的配置,进行延后处理;
减少在 viewDidLoad 里的耗时处理;
3.3、针对阶段三的优化:
UITabBarController 第一个子控制器里的一些耗时操作可以放到 viewDidAppear 方法中,先把界面加载出来,然后再拿到数据刷新界面;
增加广告页,在这个时间内准备好首屏页面和数据;
4、耗时检测工具
Xcode自带的 Time Profiler,具体操作可以参考这篇文章;
查询main函数之前的耗时:菜单:Product->Scheme->Edit Scheme->Environment Variables,设置:key:DYLD_PRINT_STATISTICS ,value:1。
三、耗电优化
要研究耗电优化,就要先明白耗电产生的原因,然后针对性的做出一些优化。根据耗电的原因和可优化类型,可以分为 CPU 和 GPU 操作优化、网络优化、定位优化、动作传感器优化和蓝牙优化五大类。
1、CPU 和 GPU 操作优化
CPU 和 GPU 消耗是所有开发者绕不开的难关,良好的开发习惯,能让我们减少很多能耗,这里可以将前面介绍的界面优化结合起来,除此之外,有几个地方可以进行优化:
少用定时器,每次用的时候都仔细思考下:首先,能不能用其他方式代替,比如定时刷新数据改为触发式刷新,还有dispatch source代替定时器监测文件变化;如果不能代替,那么注意的是要设置一个合适的超时时间,以及在不再需要时及时关闭重复性定时器。
文件的读写操作很耗电,我们要尽量减小读写操作的频率,思路有:
小数据和小改动最好批量一次性写入;使用 SQLite 或 Core Data 存储大量的数据;
2、网络优化
使用断点续传,否则网络不稳定时可能多次传输相同的内容
减少、压缩网络数据
让用户可以取消长时间运行或者速度很慢的网络操作,设置合适的超时时间
网络不可用时,不要尝试执行网络请求
批量传输,比如,下载视频流时,不要传输很小的数据包,直接下载整个文件或者一大块一大块地下载。如果下载广告,一 次性多下载一些,然后再慢慢展示。如果下载电子邮件,一次下载多封,不要一封一封地下载
3、定位优化
很多 APP 为了记录用户的活动或者提供基于位置的服务会进行定位。定位精度越高,定位时间越长,消耗电量也就越多。所以 APP 应该尽量降低定位精度、缩短定位时间。不需要位置信息之后立即停止定位。
如果只是需要快速确定用户位置,最好用 CLLocationManager的requestLocation 方法。定位完成后,会自动让定位硬件断电
如果不是导航应用,尽量不要实时更新位置,定位完毕就关掉定位服务
尽量降低定位精度,比如尽量不要使用精度最高的 kCLLocationAccuracyBest
需要后台定位时,尽量设置 pausesLocationUpdatesAutomatically 为 YES,如果用户不太可能移动的时候系统会自动暂停位置更新
尽量不要使用 startMonitoringSignificantLocationChanges,优先考虑 startMonitoringForRegion
3、动作传感器优化
用户移动、摇晃、倾斜设备时,会产生动作(motion)事件,这些事件由加速度计、陀螺仪、磁力计等硬件检测。在不需要检测的场合,应该及时关闭这些硬件
长时间用不上加速度计、陀螺仪、磁力计等设备的动作数据时,应该停止更新数据,不然也会浪费电能。应按需获取,用完即停。
4、蓝牙优化
没有必要的时候不要扫描蓝牙外设。
扫描外设时一般不要用 CBCentralManagerScanOptionAllowDuplicatesKey。
只查找你需要的外设服务。外设可能提供很多服务和特性(characteristic),查找外设的时候可以指定 UUID。
不要轮询设备特性值,用通知监测特征值的变化。
特性值不再提供通知或者不再需要通信的时候就断开连接。
5、耗电检测
在Xcode中选择View > Navigators > Show Debug Navigator,这里提供了很多仪表用于分析功耗。Energy impact可以查看正在运行的app的功耗
启动 Instruments,选择你的设备和要检测的 APP,打开 Energy Log 进行检测
设备上进入设置 > 开发者 > Logging > 开启功耗记录。注意:如果手机里没有开发者选项请看这里
四、安装包瘦身
安装包的大小受资源文件和可执行文件影响,所以针对性的优化也是这两方面。
1、资源文件优化
在项目中引入图片时候,直接在 Assets.xcassets 中添加就可以,这样能使用到 App Slicing 功能,这样当用户从 App Store上下载 App 时,可以只下载适用于其设备的 App 架构版本和所需资源,从而减少App所占的空间,且现在基本没有 1x 屏幕的设备了,所以可以不用提供这个分辨率的图片。
使用 LSUnusedResources 查找无用图片
使用 TinyPNG有 损压缩图片
启动图片一般较大,可以用 LaunchScreen.storyboard 替换
音频文件、视频文件和 H5 远端化
2、可执行文件优化
清理无用代码--AppCode,用它的 inspect code 来扫描无用代码,包括无用的类、函数、宏定义、value、属性等
去掉异常支持,Enable C++ Exceptions、Enable Objective-C Exceptions 设置为 NO, Other C Flags 添加 -fno-exceptions
在 release 状态下,Strip Debug Symbols During Copy、Strip Linked Product、Make String Read-Only、Dead Code Stripping、Deployment PostProcessing、Symbols hidden by default 设为Y ES,Optimization Level 设置为 Fastest、Smallest
OC项目中使用 Swift,会增加安装包大小,因为 FrameWork 中会加入为了支持 Swift 的动态库集合,如果纯 Swift 项目,不会引入这些东西。
删除不使用的三方库,功能用的少但是体积大的三方库可以考虑自己重写,合并功能重复的三方库
最后郑重声明,本篇里只是记录一下我的个人总结,资料大量参考了以下文章:
iOS 保持界面流畅的技巧
今日头条iOS客户端启动速度优化;
iOS App 启动性能优化;
iOS App冷启动治理:来自美团外卖的实践;
iOS app启动速度研究实践;
iOS进阶--App功耗优化看这篇就够了;
iOS的性能优化;
iOS安装包瘦身小记;
iOS App 安装包瘦身指南
欢迎大家来我的小窝做客啊,里面记录下了我进步的点点滴滴,一切逆境只是前进的理由,与君共勉。