当前位置: 首页 > 科技观察

App启动速度优化从

时间:2023-03-12 16:24:39 科技观察

应用程序启动流程来看,iOS应用程序的启动分为pre-main阶段和main()阶段,其中系统按顺序进行:pre-main阶段1.1.加载应用程序的可执行文件1.2.加载动态链接库加载器dyld(dynamicloader)1.3。dyld递归加载所有依赖的dylib(动态库动态链接库)main()阶段2.1。dyld调用main()2.2。调用UIApplicationMain()2.3。调用applicationWillFinishLaunching2.4。调用didFinishLaunchingWithOptions开始耗时测量在优化之前,我们首先要能够测量每个阶段的耗时。1.Pre-mainstage对于pre-mainstage,苹果提供了一种测量方法,在Xcode中,Editscheme->Run->Augments设置环境变量DYLD_PRINT_STATISTICS为1,之后控制台会输出类似的内容:Totalpre-maintime:228.41milliseconds(100.0%)dylibloadingtime:82.35milliseconds(36.0%)rebase/bindingtime:6.12milliseconds(2.6%)ObjCsetuptime:7.82milliseconds(3.4%)initializertime:132.02milliseconds(57.8%)最慢的初始化器:libSystem.B.dylib:122.07milliseconds(53.4%)CoreFoundation:5.59milliseconds(2.4%)这样我们就可以清楚的看到每次耗时。2.main()阶段mian()阶段主要是测量从mian()函数开始到didFinishLaunchingWithOptions执行结束的时间,我们直接插入代码即可。CFAbsoluteTimeStartTime;intmain(intargc,char*argv[]){StartTime=CFAbsoluteTimeGetCurrent();然后在AppDelegate.m文件中使用extern声明全局变量StartTimeexternCFAbsoluteTimeStartTime;***在didFinishLaunchingWithOptions中,得到当前时间与StartTime的差值,即为main()阶段的运行时间。doublelaunchTime=(CFAbsoluteTimeGetCurrent()-StartTime);改善pre-main阶段的启动时间在这个阶段,我们可以做的是优化dylib加载Dylib。之前说过,loadingsystem的dylib因为优化的缘故,速度非常快。但是加载内嵌的dylib文件比较耗时,所以尽量将多个内嵌的dylib合并成一个加载,或者使用静态存档。不建议在运行时使用dlopen()进行延迟加载,它可能会导致一些问题,并且整体开销较高。之前提到Rebase/Binding,Rebaing在I/O上消耗的时间比较多,后面的Binding不需要太多的I/O,而是花时间在计算上。所以这两个步骤的时间消耗是混在一起的。之前说过可以在__DATA段中查看需要修复(fix-up)的指针,所以减少指针的个数会减少这部分工作的耗时。对于ObjC来说,就是减少Class、selector、category等元数据的数量。编码原则和设计模式等理论会促使你写出更精巧短小的类和方法,将方法的每一部分单独归为一个类,这实际上会增加启动时间。对于C++虚拟方法需要减少,因为虚拟方法创建vtables,它也在__DATA部分中创建结构。虽然C++虚方法启动时间的增加比ObjC元数据少,但仍然不容忽视。Objc设置大多数ObjC初始化工作已经在Rebase/Bind阶段完成。这一步dyld会注册所有声明的ObjC类,将分类插入到类的方法列表中,然后检查每个选择器的唯一性。这一步没有什么需要优化的。Rebase/Bind阶段进行了优化,这一步的耗时也会减少。Initializers这个阶段dyld开始运行程序的初始化函数,调用各个Objc类和分类的+load方法,调用C/C++中的构造函数(用attribute((constructor)修饰的函数),创建C++非原始类型的静态全局变量,执行完Initializers阶段后,dyld开始调用main()函数,在这一步,我们可以做的优化有:类的+load方法少做一些事情,尝试将这些东西推迟到+initiaailize减少构造函数的数量,在构造函数中少做一些事情来减少C++staticglobal变量的数量main()阶段的优化这个阶段的优化主要是减少didFinishLaunchingWithOptions中的工作方法。在didFinishLaunchingWithOptions方法中,我们将创建应用程序的窗口,指定其rootViewController,并调用窗口的makeKeyAndVisible方法使其可见e.由于业务需要,我们会初始化各个二方/第三方库,设置系统UI风格,检查是否需要显示引导页,是否登录,是否有新版本等。由于由于历史原因,这里的代码往往会变得比较大。启动耗时难以控制。所以,在满足业务需求的前提下,didFinishLaunchingWithOptions在主线程中做的事情越少越好。在这一步,我们可以做的优化包括:整理各种二方/第三方库,找到可以懒加载的库??,做懒加载处理,比如放在首页的viewDidAppear方法中控制器。梳理业务逻辑,对可以延迟的逻辑做延迟执行。检查新版本、注册推送通知等逻辑。避免复杂/冗余的计算。避免在主页控制器的viewDidLoad和viewWillAppear中做太多事情。执行完这两个方法,就可以显示首页控制器了。一些可以延迟创建的视图应该以延迟/延迟加载的方式创建。主页控制器是用纯代码构建的。综上所述,启动速度优化似乎就一句话:让系统在启动时少做一些工作。当然,我们首先要了解项目在启动时都做了哪些事情,对启动速度的影响有多大,然后对项目代码逐个分析,让系统在启动时更容易一些。