性能调优工作流程

通过上图我们可以看出性能调试的过程:
- 重现性能问题(如:反应慢,动画卡顿等),利用Profile工具或经验查找问题原因
- 通过观察提出存在问题的假设,
- 反复观察Profile工具表现,确定正确的假设
- 根据假设改进程序
- 改进之后再次观察应用查看问题是否存在
应用启动
看门狗程序的时限
从某种意义上说,应用启动的性能是给用户的第一印象,所以启动时间越短给用户的第一印象越好,一般很少有人会注意,系统的看门狗程序对你的应用制定了一系列的时限,如果特定场景下应用响应时间超出了这些时限,看门狗程序会直接结束你的应用,当然另外一种情况是用户在启动应用时不想再等下去了,直接手动结束了应用启动。下面列举了系统看门狗规定的场景及时限。
场景 | 时限 |
---|---|
Launch | 20s |
Resume | 10s |
Suspend | 10s |
Quit | 6s |
Backgournd task | 10m |
注意:
Xcode在调试的时候会禁掉系统看门狗程序
如何衡量启动时间
输出系统时间
- 在
main()
函数中记录系统开始启动的时间
1 | int main(int argc, char **argv) { StartTime = CFAbsoluteTimeGetCurrent(); |
- 应用启动完成时输出启动所用的时间
1 | - (void)applicationDidFinishLaunching:(UIApplication *)app { dispatch_async(dispatch_get_main_queue(), ^{ NSLog(@"Launched in %f sec", CFAbsoluteTimeGetCurrent() - StartTime); }); |
利用Time Profile确定启动时间
- 切换到Sample List找到
main()
函数,即为启动开始的时间,如下图:

- 找到
didFinishLaunchWithOptions
,即为启动结束的时间,如图:

应用启动步骤
链接和加载
介绍:
链接加载发生在Time Profiler中的dyld阶段,该阶段library将被映射进相应的内存空间,然后进行绑定和修复,最后进行一些静态变量的初始化。
建议:
- 由于每一个Objective-C的framework映射进内存都需要时间的,所以避免链接无用的framework
- 被标记为Optinal的framework需要额外的判断工作,所以不要把所有系统版本都需要的framework标记为Optinal
UIKit初始化
介绍:
该阶段会对与视图相关的内容进行初始化如:Fonts,status bar,user defaults,main nib等
这些内容的初始化是在以下过程中完成的:
1 | UIApplicationInitialize UIApplicationInstantiateSingleton -[UIApplication _createStatusBarWithRequestedStyle: ...] -[UIApplication _loadMainNibFileNamed:bundle:] |
建议:
- 尽量减少不必要的控件添加到nib中
- 偏好设置(User Default)是用于存储属性的,并且它是被一次性反序列化的,所以不要试图在其中存储过大的数据,比如:图片,文件等。
处理application delegate method
介绍:
UIKit会在一下回调中调用你的代码来完成程序的启动
- 调用
application:willFinishLaunchingWithOptions:
- 恢复应用的状态
- 调用
application:didFinishLaunchingWithOptions:
经过以上步骤应用会完全在你的代码控制之下
建议:
- 由于
application:didFinishLaunchingWithOptions
回调的执行也属于应用启动的过程,所以应该尽量避免在该回掉中进行一些费时的操作,如:复杂数据的处理、I/O的操作等。
第一次动画事物
介绍:
由于应用启动准备任务完成之后,最终会将应用的内容呈现在屏幕上,而呈现的方式就是一个视图从小到大的动画,所以这一步的优化就变成了针对动画的优化
针对于动画的优化下一章节我们将详细讲解。
绘图和动画
影响动画性能的因素
根据这篇文章的介绍,动画执行过程中我们能够控制的阶段是下图高亮的阶段:

引起动画性能问题的因素主要有以下两点:
- 布局
- 复杂的视图继承关系
- 懒加载视图
- 通过访问数据库得到的数据来初始化视图
- 绘制
- -drawRect 如非必要,尽量不要重载drawRect方法
- 字符绘制 如:UILabel控件最终是绘制出来的
- 图片解码 针对图片视图的动画,commit之前对图片的解码是必不可少的步骤,所以推荐使用png/jpeg格式的图片,苹果对这两种图片的解码做了内部的优化
改善动画性能的关键点
尽量减少设置操作
- 在进行布局的时候,尽量避免重CPU(如复杂的浮点运算)或阻塞操作
- 使用内存缓存,将多次用到的属性或对象存储在内存中,静态变量或单例等
- 确保数据库有合适的索引以供查询
- 尽可能复用cell或view
减少绘制
- 只在需要的时候调用
-setNeedsDisplay
进行重绘 - 如非必要不要重载
-drawRect:
- 尽量使用
-setNeedsDisplayInRect
缩小重绘区域 - 如果不需要交互的话,使用CALayer的属性代替视图的属性
高效的使用image
图像和图层:
- UIImage是CGImage的轻量级的扩展
- CALayer的contents属性可以指向CGImage
- CGImage是以文件或数据形式进行存储的,但最终是以位图的形式加载进内存的
与[UIImage draw…]相比UIImageView做了如下优化:
- CATrasaction可以从CGImage直接获取位图
- 允许通过GPU进行混合
- 内建的位图缓存
一些建议:
- 保证图片与视图的size相符合
- 尽量不设置图片的alpha属性,图层混合需要额外消耗性能
- 只使用PNG或JPEG格式的图片
PNG与JPEG图片格式:
PNG采用的是无损压缩,JPEG采用的经过优化的压缩算法;
PNG存在alpha通道,JPEG没有alpha通道;
JPEG的文件相对较小,JPEG图片解码的时间相对较短
xcode针对PNG的一些优化:
- 预乘alpha(抗锯齿渲染的一种技术)和字节转换(byte-swap)
- 禁止一些PNG的混合模式
- 对一张图片可以进行并行解码,加快解码速度
图像的缓存:
- 当image被绘制进一个位图上下文时
- [UIimage imageNamed:]方法会将image缓存进可擦除的内存中
- [UIImage imageWithContentsOfFile:]则不会对image进行缓存
- 所有CGImage被赋值给layer的contents属性时,都会被系统缓存起来
- kCGImageSourceShouldCache
- 标识image是否会被缓存进解码表(decoded form)中
- 一般情况下不要试图自己缓存image
-drawsAsynchronously
- 当layer的该属性被设置为YES的时候,被传递给
-drawInContent:
方法的CGContent对象会将提交给它的绘图指令进行序列化存储,然后异步的去执行这些绘图操作(也就是说-drawInContent:
方法会异步的执行)。这样可以使得layer的绘图操作推迟一会儿执行,不会等到它绘图操作完成之后再往下执行。默认值是NO