启动时间
启动时间可谓是用户对你的App的第一印象,用户好不容易下载了App,然后饶有兴致的开打App,启动时间过长很可能会让用户直接把App打入冷宫。就算用户非常有耐心,苹果的watch dog机制也会kill掉启动时间过长的App,这种情况下给用户的感觉就是这App怎么一启动就卡死然后崩溃了,然后无情卸载。这里还要说一下,Xcode在debug模式下是没有开启watch dog的,所以不要以为调试时候没问题就真的没问题了,一定要在真机上测试一下。
首先我们了解一下App的启动流程
通过实际的调试,我们得到各个函数的调用顺序如下:
- 启动页
- main()
- UIApplicationMain()
- willFinishLaunchingWithOptions()
- didFinishLaunchingWithOptions()
- loadView()
- viewDidLoad()
- applicationDidBecomeActive()
启动页是在main()
函数调用之前出来的,main()
是程序的入口,里面调用了UIApplicationMain()
。当App从didFinishLaunchingWithOptions()
返回的时候,实际的UI立刻开始加载,但是在applicationDidBecomeActive()
这个回调完成之前,UI即使已经初始化,但仍旧被阻塞着。
总的启动时间T包括main()
调用之前的pre-main time
T1,加上从main()
到applicationDidBecomeActive()
的时间T2。
获取启动时间
我们可以通过环境变量的方法来获取pre-main time
。打开Xcode->Product->Scheme->Edit Scheme
或者直接command+shift+<
(在键盘上是逗号,按住shift就是小于号了)。在Edit Scheme
中添加DYLD_PRINT_STATISTICS
这个环境变量,如果要打印详细的时间分布,可以将value设为1
运行项目之后就会在控制台会打印出每个阶段都耗时多少
dylib loading time:
加载动态库
rebase/binding time:
修正指针和数据。此外,因为Objc是一种动态语言,因此需要注册类名与类相关信息的一张注册表,对在其他dylib中定义的category,也需要通过rebasing和binding来修正扩展方法的地址来保证selector的唯一性。关于rebase/binding 这块,有兴趣的小伙伴可以看看这篇文章ObjC setup time:
ObjC类初始化
initializer time:
其他初始化,如上图,细分为其他的几个部分
libMainThreadChecker:
debug时候检查线程的
了解完毕mian()函数之前加载的步骤后,我们可以简单的分析出影响T1时间的各种因素:
1. 动态库加载越多,启动越慢
2. ObjC类,方法越多,启动越慢
3. ObjC的+load越多,启动越慢
4. C的constructor函数越多,启动越慢
5. C++静态对象越多,启动越慢
我们已经获取到了main()
函数之前的启动时间T1,至于T2,我们可以使用Xcode自带工具Instruments里面的Time Profiler来获取,也可以在main()
的第一句和applicationDidBecomeActive()
的最后一句加上获取时间的代码CFAbsoluteTimeGetCurrent()
,这里就不细说了。
Time Profiler
工具通过Xcode工具栏中Product->Profile(command+i)
可以启动,(也可以通过Xcode->Open Developer Tool->Instruments
)启动后界面如下:
选择Time Profiler
,打开后如图:
点击左上角红色按钮运行,勾选左下角Call Tree
中Separate Thread
和Hide System Libraries
,等到第一个页面显示出来的之后,点击左上角暂停按钮,下面就会统计出每个步骤的耗时情况。这个时候我们就可以很容易得到启动时间T2。
优化启动时间
针对T1的各个阶段分别进行优化处理
1.dylib loading time
- 核心思想是减少dylibs的引用
- 合并现有的dylibs
- 使用静态库
2.rebase/binding time
- 核心思想是在进行动态库的重定位和绑定(Rebase/binding)过程中减少指针修正;
- 减少Objective-C类数量,减少分类,减少实例变量和函数(删除不用的类以及冗余代码,再深一点就是减少第三方工具的使用,可以查看源码,自己实现);
- 减少C++虚函数;
- 多使用Swift结构体(推荐使用swift)
3.ObjC setup time
核心思想同上,主要是类的注册,分类的注册,唯一Selector。这部分内容基本上在上一阶段优化过后就不会太过耗时。
4.initializer time
- 使用initialize替代load方法
- 减少使用c/c++的attribute((constructor));推荐使用dispatch_once(),pthread_once(), std:once()等方法
- 不要在初始化中创建线程
- 推荐使用swift
针对T2进行优化
我们通过Time Profiler拿到每个步骤的耗时之后,右下角的 Heaviest Trace 可查看比较消耗CPU的代码,双击点击进去可查看到对应的代码,进行修改。有些操作可以延后执行,或者异步执行等,这些需要根据自己的业务逻辑在处理。