美文网首页
iOS启动性能优化篇

iOS启动性能优化篇

作者: hui8685291 | 来源:发表于2021-03-18 20:36 被阅读0次

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

不应放过的一些小细节

异步操作并不影响指标,但有可能影响交互体验,例如大量网络请求导致数据拥堵

有时候一些交互上的优化比技术手段效果更明显,视觉上的快决不是冰冷的数据可以解释的,好好和你们的设计师谈谈动画

相关文章

网友评论

      本文标题:iOS启动性能优化篇

      本文链接:https://www.haomeiwen.com/subject/lsinoktx.html