iOS性能优化
iOS性能优化,可以从启动时间、挂起(hang)、卡顿(hitch)、内存、电池使用、硬盘读写等方面进行优化。
启动时间
启动类型
app的启动类型有三种:1.Cold;2.Warm;3.Resume;Cold启动,一般发生在手机刚开机,或者一个应用很久没有打开过的情况。Warn启动,一般是app退出不久,又重新打开的情况。Resume,是app进入后台,然后重新进入前台的情况。
Warm和Resume一般都能很快启动,Cold启动是最慢的。所以优化app的启动时间,主要是考虑Cold启动的情况。
最优启动时间
如果一个app的Cold启动时间在400ms以内,那么用户基本感觉不到卡顿,所以小于400ms是最优启动时间。
app启动阶段
Cold启动可以分为六个阶段:
- System Interface。这个阶段主要是dyld的工作,加载动态库的操作会影响这个阶段的时间,如果这个阶段时间过长,可以考虑移除没有使用的动态库,或者减少动态库的使用;
- Runtime init。这个阶段是runtime的初始化,如果在
+[NSObject load]
中做了事情,会消耗时间。另外过多的使用分类也会消耗时间; - UIKit init。这个是固定的消耗,不需要优化;
- Application init。
- application: didFinishLaunchingWithOptions:
中的操作损耗的时间。 - Initial frame render。第一帧画面渲染消耗的时间。第一个页面的View的层级,自动布局,和其他的一些阻塞主线程的操作,都是影响这个阶段的时间。
- Extended。 不是每个app都有的阶段。一般和启动后异步加载数据有关。
所有能优化app启动时间的方法,都在上面的六个阶段里。
app启动时间测试
Instruments和XCTest都有测试app启动时间的功能,此外Metric Kit也可以统计app在用户使用时的启动时间。
卡顿(hitch)
卡顿发生在ScrollView滚动、动画效果和页面转场动画中,当你发现滚动或者动画“不怎么流畅”,那就是卡顿了。
在滚动或者动画时,对于60fps的屏幕,要求是每16.67ms显示新的一帧。如果新的一帧没有能在16.67ms内准备好,那么屏幕下一个16.67ms内显示的还是旧的那一帧,就会导致卡顿。
触发每一帧开始渲染事件的硬件叫做VSYNC,在60fps的屏幕上,VSYNC每16.67ms触发一次渲染事件,渲染事件发生后,新的一帧必须已经准备好,否则就会产生卡顿。
要解决卡顿问题,我们先要知道在滚动或者动画时,每一帧是怎么渲染的。渲染的过程,叫做Render Loop,由以下六个阶段组成:
-
Event。这个阶段,app接受到了事件(触摸/网络回调/按压键盘/定时器等),并且做出响应。通常的响应方式有:改变图层大小/位置/颜色,甚至改变图层结构等等。
-
Commit。处理上一阶段图层的设置,例如AutoLayout,drawRect等等。
-
Render prepare。来到这一步,所有图层已经布局并绘制好了,Render Server在这一步为渲染成真正的图片做好准备。
-
Render execute。渲染图片,大名鼎鼎的离屏渲染会发生在这一步。
-
Display。图片已经完成渲染,VSYNC事件到来时就可以显示了。
根据Render loop,导致卡顿的原因可以分为两种类型:
-
Commit hitch。表示app花了太多时间来处理Event或者Commit。
-
Render hitch。表示花了太多时间来render。
导致Commit hitch的原因,一般是AutoLayout耗时太多,或者做了太多图层的添加或插入等改变图层的操作,或者drawRect耗时太多。
导致Render hitch的原因,一般是离屏渲染。
卡顿的检测
可以通过Instruments的Animation Hitchs或者MetricKit来检测卡顿。
挂起(hang)
用户点击屏幕后,如果用户界面超过1秒以上没有任何反应,这种情况叫做挂起(hang),就是我们平时说的“卡死了”。挂起非常影响用户体验。
发生挂起的原因
挂起的发生和主线程的runloop有关。主线程的runloop,最重要的功能,就是响应用户触摸事件,处理触摸事件,然后更新界面。如果处理触摸事件的时间过长,就会导致挂起。
导致主线程处理时间过长,主要可以分为两种原因:
- 主线程执行的任务太耗时。例如网络请求、IO操作、负复杂的视图布局等。
- 第二种是多线程环境。尤其是使用锁,信号量等,导致主线程等待。
所以主线程应该只专注于更新UI任务。
挂起的检测
开发阶段,可以使用Instruments中的Time Profile来检测挂起。发布阶段,可以使用MetricKit来检测。
挂起的修复
现在我们知道,挂起是由于主线程执行耗时任务导致的,所以挂起的修复也很明确:
- 对于不需要在主线程中执行的任务,放到子线程中执行;
- 优化必须在主线程中执行的任务,使它们更快完成。
内存
内存是有限的资源,如果过多地占用内存,会导致app卡顿,甚至有被系统终止的风险。所以,我们在开发app时,要关注我们app的内存是否过多的使用。
在开发阶段,在Xcode的Debug窗口,我们很容易就能看到app当前内存的使用情况。
![](https://img.haomeiwen.com/i4633505/8c4af290c1b59d79.png)
如果发现内存占用过高,就要考虑优化一下内存的使用了。如上图所示,当前只用了20.6MB的内存,指针在绿色区域,所以是比较健康的。如果指针在黄色或者红色区域,就要考虑优化内存了。
如果内存过高时,可以通过查看Memory Graph Hierarchy来分析内存的使用细节。
![](https://img.haomeiwen.com/i4633505/1ab77060d736292a.png)
在Xcode中查看Memory Graph Hierarchy不够灵活,我们可以把Memory Graph Hierarchy导出到一个.memgraph
文件,在终端中通过vmmap、leaks、heap、malloc_history等工具对该文件进行分析。
使用vmmap获取当前 a.memgraph 文件的概览
vmmap -summary a.memgraph
使用leaks检查内存泄露:
leaks a.memgraph
使用heap显示分配在进程堆上的对象:
heap -sortBySize a.memgraph
使用malloc_history检查某个内存的创建过程:
malloc_history a.memgraph [address]
通过上面的工具,就有可能定位到导致大量内存占用的代码了。
注意图片
在iOS中,占用内存最大的对象,一般是图片!
一张590KB,尺寸为2048*1536的JPEG图片,在app中显示时,可以占用10MB的内存!因为每个像素点占4个字节,2048乘以1536然后乘以4字节,就会达到 10MB!
需要重点关注图片的尺寸,尺寸越大,占用的内存越大!其实手机的屏幕上,根本不需要太大的图片的,所以往往缩小图片的尺寸是常见的降低内存占用的手段。
缩小图片尺寸,需要用
Image I/O
框架,不要用UIImage的方法!因为UIImage的方法还是会先把图片解码,内存占用还是很大。
因为图片占用的内存实在太大,所以,在看不到图片的时候,最好把图片释放,等到需要展示图片的时候再加载回来。
例如,在ViewController中可以这样处理:
- (void)viewDidDisappear:(BOOL)animated {
[super viewDidDisappear:animated];
// 释放图片
[self unloadImages];
}
- (void)viewWillAppear:(BOOL)animated {
// 加载图片
[self loadImages];
[super viewWillAppear:animated];
}
此外,还可以通过监听UIApplicationDidEnterBackgroundNotification
和UIApplicationWillEnterForegroundNotification
广播,在app进入后台后释放图片,在app回到前台时加载图片。
电池使用
主要是降低app对电池的损耗。主要一下四个方面减少电量的损耗:
-
支持dark mode。支持dark mode是能显著减低电池损耗的方式,官方宣称可以节省70%的电池损耗!
-
确保使用合理的屏幕帧率。现在有些iOS设备的屏幕帧率可以去到120fps,高帧率意味着高电池消耗,其实大部分场景都不需要这么高的帧率。核心是审计屏幕上的次要内容的帧率不能高于主要内容的帧率。例如,一个很简单的页面,主要的内容使用30fps的帧率就足以显示了,但是这个页面有一个次要元素,使用了60fps的动画效果,那么这个次要元素会导致整个屏幕使用的是60fps的屏幕帧率。这种情况,需要降低整个次要元素动画的帧率为30fps,从而减少电量的消耗。
-
管理好后台任务。如果app使用后台定位、后台音频播放这些后台任务,就要处理好这些任务的使用情况,防止在后台持续消耗电量。
-
将一些非用户触发且不需要及时响应的任务延迟到适当的时候执行。例如:1. 自动下载任务(像预先缓存一些电视节目),可以使用NSURLSession的后台下载;2. 苹果推送(APNs),降低一些不重要的通知的优先级。
硬盘读写
略略略。
网友评论