APP启动
APP的启动方式
- 冷启动(Cold Launch):从零开始启动APP
- 热启动(Warm Launch):APP已经在内存中,挂在后台,再次点击APP图标启动APP
App启动可分为以下几个阶段:
main()函数之前;
main()函数之后;
main()函数之前
在不越狱的情况下,以往很难精确的测量在main()函数之前的启动耗时,因而我们也往往容易忽略掉这部分数据。小型App确实不需要太过关注这部分。但如果是大型App(自定义的动态库超过50个、或编译结果二进制文件超过30MB),这部分耗时将会变得突出。所幸,苹果已经在Xcode中加入这部分的支持。
苹果提供的方法
在Xcode的菜单中选择Project→Scheme→Edit Scheme...,然后找到 Run → Environment Variables →+,添加name为DYLD_PRINT_STATISTICS value为1的环境变量。
image.png
在Xcode运行App时,会在console中得到一个报告。例如,会得到这样一个报告:
Total pre-main time: 94.33 milliseconds (100.0%)
dylib loading time: 61.87 milliseconds (65.5%)
rebase/binding time: 3.09 milliseconds (3.2%)
ObjC setup time: 10.78 milliseconds (11.4%)
initializer time: 18.50 milliseconds (19.6%)
slowest intializers :
libSystem.B.dylib : 3.59 milliseconds (3.8%)
libBacktraceRecording.dylib : 3.65 milliseconds (3.8%)
GTFreeWifi : 7.09 milliseconds (7.5%)
解读
main()函数之前总共使用了94.33ms
在94.33ms中,加载动态库用了61.87ms,指针重定位使用了3.09ms,ObjC类初始化使用了10.78ms,各种初始化使用了18.50ms。
在初始化耗费的18.50ms中,用时最多的三个初始化是libSystem.B.dylib、libBacktraceRecording.dylib以及GTFreeWifi。
main()函数之后
从main()函数开始至applicationWillFinishLaunching结束,我们统一称为main()函数之后的部分。
影响启动性能的因素
main()函数之前耗时的影响因素
动态库加载越多,启动越慢。
-
ObjC类越多,启动越慢
-
C的constructor函数越多,启动越慢
-
C++静态对象越多,启动越慢
-
ObjC的+load越多,启动越慢
实验证明,在ObjC类的数目一样多的情况下,需要加载的动态库越多,App启动就越慢。同样的,在动态库一样多的情况下,ObjC的类越多,App的启动也越慢。需要加载的动态库从1个上升到10个的时候,用户几乎感知不到任何分别,但从10个上升到100个的时候就会变得十分明显。同理,100个类和1000个类,可能也很难查察觉得出,但1000个类和10000个类的分别就开始明显起来。
同样的,尽量不要写attribute((constructor))的C函数,也尽量不要用到C++的静态对象;至于ObjC的+load方法,似乎大家已经习惯不用它了。任何情况下,能用dispatch_once()来完成的,就尽量不要用到以上的方法。
main()函数之后耗时的影响因素
-
执行main()函数的耗时
-
执行applicationWillFinishLaunching的耗时
-
rootViewController及其childViewController的加载、view及其subviews的加载
APP的冷启动可以概括为3大阶段
dyld
runtime
main
image.png
APP的启动 - dyld
dyld(dynamic link editor),Apple的动态链接器,可以用来装载Mach-O文件(可执行文件、动态库等)
启动APP时,dyld所做的事情有
装载APP的可执行文件,同时会递归加载所有依赖的动态库
当dyld把可执行文件、动态库都装载完毕后,会通知Runtime进行下一步的处理
APP的启动 - runtime
启动APP时,runtime所做的事情有
调用map_images进行可执行文件内容的解析和处理
在load_images中调用call_load_methods,调用所有Class和Category的+load方法
进行各种objc结构的初始化(注册Objc类 、初始化类对象等等)
调用C++静态初始化器和attribute((constructor))修饰的函数
到此为止,可执行文件和动态库中所有的符号(Class,Protocol,Selector,IMP,…)都已经按格式成功加载到内存中,被runtime 所管理
小结一下
APP的启动由dyld主导,将可执行文件加载到内存,顺便加载所有依赖的动态库
并由runtime负责加载成objc定义的结构
所有初始化工作结束后,dyld就会调用main函数
接下来就是UIApplicationMain函数,AppDelegate的application:didFinishLaunchingWithOptions:方法
APP的启动优化
按照不同的阶段
dyld
减少动态库、合并一些动态库(定期清理不必要的动态库)
减少Objc类、分类的数量、减少Selector数量(定期清理不必要的类、分类)
减少C++虚函数数量
Swift尽量使用struct
runtime
用+initialize方法和dispatch_once取代所有的attribute((constructor))、C++静态构造器、ObjC的+load
main
在不影响用户体验的前提下,尽可能将一些操作延迟,不要全部都放在finishLaunching方法中
按需加载
总结:
利用DYLD_PRINT_STATISTICS分析main()函数之前的耗时
重新梳理架构,减少动态库、ObjC类的数目,减少Category的数目
定期扫描不再使用的动态库、类、函数,例如每两个迭代一次
用dispatchonce()代替所有的attribute((constructor))函数、C++静态对象初始化、ObjC的+load
在设计师可接受的范围内压缩图片的大小,会有意外收获
利用锚点分析applicationWillFinishLaunching的耗时
将不需要马上在applicationWillFinishLaunching执行的代码延后执行
rootViewController的加载,适当将某一级的childViewController或subviews延后加载
如果你的App可能会被后台拉起并冷启动,可考虑不加载rootViewController
不应放过的一些小细节
异步操作并不影响指标,但有可能影响交互体验,例如大量网络请求导致数据拥堵
有时候一些交互上的优化比技术手段效果更明显,视觉上的快决不是冰冷的数据可以解释的,好好和你们的设计师谈谈动画
网友评论