iOS 优化程序冷启动时间

文章目录
       一、何为冷启动
       1、冷启动
       2、热启动
       二、冷启动时间
       1、什么是冷启动时间
       2、冷启动过程做了什么 
       三、pre-main()阶段
       1、pre-main阶段加载
       2、pr-main节点时间测量及其优化
       四、main()阶段
       1、main()阶段加载
       2、main()优化

一、何为冷启动

1、冷启动: app第一次启动的过程(或者app被kill后,重新启动的过程)。

2、热启动: app处于悬挂状态,被重新切换回app的过程。

二、冷启动时间

1、什么是冷启动时间: 简而言之,就是用户点击app的iocn那一刻开始到看到第一个界面的这段时间。

2、冷启动过程做了什么

将其划分为两部分,一个是pre-main,另一部分是main

1.pre-main阶段
    1.1 加载应用的可执行文件
    1.2 加载动态链接库加载器dyld(dynamic loader)
    1.3 dyld递归加载应用所有依赖的dylib(dynamic library 动态链接库)
        包括iOS系统的以及APP依赖的第三方库。
    
2.main阶段    
   2.1 dyld调用main() 
   2.2 调用UIApplicationMain() 
   2.3 调用applicationWillFinishLaunching(这个一般用不到)
   2.4 调用didFinishLaunchingWithOptions

三、如何查询冷启动时间

1、pre-main阶段加载
具体的加载步骤如下:
(1)加载dylib,分析每个dylib(大部分是iOS系统的),找到其Mach-O文件,
打开并读取验证有效性,找到代码签名注册到内核,
最后对dylib的每个segment调用mmap()。

(2)rebase/bind (指针重定向):
dylib加载完成之后,它们处于相互独立的状态,需要绑定起来。
在dylib的加载过程中,系统为了安全考虑,引入了ASLR
(Address Space Layout Randomization)技术和代码签名。
由于ASLR的存在,镜像(Image,包括可执行文件、dylib和bundle)会在随机的地址上加载,
和之前指针指向的地址(preferred_address)会有一个偏差,dyld需要修正这个偏差,来指向正确的地址。
rebase在前,Bind在后。rebase做的是将镜像读入内存,修正镜像内部的指针性能消耗主要在IO。**bind做的是查询符号表,设置指向镜像外部的指针,**性能消耗主要在CPU计算

(3)ObjC setup
OC的runtime需要维护一张类名与类的方法列表的全局表。
dyld做了如下操作:
对所有声明过的OC类,将其注册到这个全局表中;
将category的方法插入到类的方法列表中
检查每个selector的唯一性
(4)initializer(这是pre-main阶段最耗时的部分)
dyld运行APP的初始化函数,调用每个OC类的+load方法,调用C++的构造器函数(attribute((constructor))修饰),创建非基本类型的C++静态全局变量,然后执行main函数。

2、pr-main节点时间测量及其优化
dyld 提供了内建的测量方法, Xcode 中 Edit scheme -> Run -> Auguments 将环境变量 DYLD_PRINT_STATISTICS 设为1或者YES。

# 优化前的
Total pre-main time: 1.4 seconds (100.0%)
         dylib loading time:  28.64 milliseconds (2.0%) 
        rebase/binding time:  41.42 milliseconds (2.9%)
            ObjC setup time:  37.08 milliseconds (2.6%)  
           initializer time: 1.3 seconds (92.4%)

分别做如下优化
(1) 移除不需要用到的动态库
(2) 移除不需要用到的类, 减少selector、category的数量
(3) 尽量避免在+load方法里执行的操作,可以推迟到+initialize方法中

# 优化后的
Total pre-main time: 1.1 seconds (100.0%)
         dylib loading time:  22.88 milliseconds (1.9%)
        rebase/binding time:  35.48 milliseconds (2.9%)
            ObjC setup time:  29.95 milliseconds (2.5%)
           initializer time: 1.1 seconds (92.5%)

四、main()阶段

1、main()阶段加载: main方法执行只有到didFinishLaunchingWithOptions执行结束这段时间。

主要工作就是初始化必要的服务,显示首页内容等。而我们的优化也是围绕如何能够快速展现首页来开展。简要来说,只需要关注这个didFinishLaunchingWithOptions方法即可。
其实在这方法里面,我们主要是初始化第三方sdk,项目配置,设置根视图控制器等。
我们可以借助打点计时器BLStopwatch来度量didFinishLaunchingWithOptions每行代码的初始时间。

例如:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions 
{
   NSLog(@"didFinishLaunchingWithOptions 开始执行");
   self.window = [[UIWindow alloc] initWithFrame:UIScreen.mainScreen.bounds];
   self.window.backgroundColor = [UIColor whiteColor];
   UITabBarController *tabVC = [[UITabBarController alloc] init];
   self.window.rootViewController =  tabVC;
   [self.window makeKeyAndVisible];
    
   BLStopwatch *timer = [BLStopwatch sharedStopwatch];
   [timer start];
   [self initShareSDK];
   [timer splitWithDescription:@"初始化分享SDK"];
   [timer stop];
   NSLog(@"%@",timer.prettyPrintedSplits);
   // #1 初始化SDK: 0.523  这是个假设时间
   return YES;
}

我们可以根据打点最后输出的时间对我们的工程进行优化。我们的目的是尽快展现第一个页面给用户。首页最好使用春代码创建,毕竟用sb还有一次解码过程

所以,为了优化启动速度,我们需要对didFinishLaunchingWithOptions必须给任务进行分级分时

* 日志、统计等必须在 APP 一启动就最先配置的事件
* 项目配置、环境配置、用户信息的初始化 、推送、IM等事件
* 其他 SDK 和配置事件

其实,总结来说,就是必须一进app就必须初始化和可以延迟初始化的。就上例而言,初始化一个SDK需要耗时0.5s,如果在该SDK 不是非必须初始化,可以放HomeVC的viewdidLoad或者viewDidAppear去做又或者需要用到才初始化。

2、main()优化
1、展示的首页尽量用纯代码创建,结合缓存更加。
2、结合BLStopwatch对启动服务进行分级分时。
3、对一些非必要的初始化操作,可以放到viewDidAppear,因为到viewDidAppear开始执行的时候,用户已经看到了APP的首屏,即宣告启动结束
4、一般仅针对测试版本进行log打印
5、建议超过0.1的都看下,是否有优化空间
6、对didFinishLaunching里的函数考虑能否挖掘可以延迟加载或者懒加载,

总之:性价比最高的优化阶段就是main函数之后的一些逻辑整理,尽量将不需要的耗时操作延迟到首屏展示之后执行。

在这里插入图片描述

©️2020 CSDN 皮肤主题: 书香水墨 设计师:CSDN官方博客 返回首页