iOS 启动过程分析及优化

APP启动过程分析

本文主要通过我们对于APP启动过程的分析,然后去剖析如何去进行启动时间的优化,以达到APP性能的提升。

一:启动方式

1.1 冷启动

App 启动时,应用进程不在系统中(初次打开或程序被杀死),需要系统分配新的进程来启动应用。

1.2热启动

App 退回后台后,对应的进程还在系统中,启动则将应用返回前台展示。

本次我们只针对于冷自动做主要分析。

二:启动流程

Apple 官方的《WWDC Optimizing App Startup Time》 将 iOS 应用的启动可分为 pre-main 阶段和 main 两个阶段,最佳的启动速度是400ms以内,最慢不得大于20s,否则会被系统进程杀死(最低配置设备)。

因此:

启动流程 = pre-main + main函数代理(didFinishLaunchingWithOptions)+ 首屏渲染(viewDidAppear)

2.1 pre-main过程

1.首先启动会加载解析APP的info.plist文件,因为该文件包含了APP加载所需要的众多配置项,例如APP启动图,是否全屏等等。
2.创建沙盒(iOS8之后,每次启动都会生成一个新的沙盒路径).
3.根据info.plist配置项检查APP所申请的权限状态。
4.加载Mach-O文件,读取dyld路径并运行动态连接器。
<1>首先dyld会寻找合适的cup运行环境。
<2>加载程序所依赖的库以及.h/.m文件编译成的.o可执行文件,并对这些库进行连接。
<3>加载所有方法,runtime就是在这个时候被初始化并完成OC的内存布局。
<4>加载C函数。
<5>加载类扩展,category,此时runtime会对所有类结构进行初始化。
<6>加载C++静态函数,加载+load方法。
<7>最后main函数被调用。

Mach-O文件是 OS X 与 iOS 系统上的可执行文件格式,类似于windows的 PE 文件。像我们编译产生的.o文件、程序可执行文件和各种库等都是Mach-O文件。

2.2main函数代理

代理函数中didFinishLaunchingWithOptions执行

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
    [[UIApplication sharedApplication] setStatusBarStyle:UIStatusBarStyleLightContent animated:YES] ;
    [self setupSDKConfig];
    
    [RTPermissionManager registerRemoteNotification:self];
    [self switchRootViewController];
    self.window.backgroundColor = [UIColor whiteColor];
    [self.window makeKeyAndVisible];
    return YES;
}

2.3首页渲染
home页面渲染。

三:影响启动时间的因素

对于APP的启动,可以说每一个时间段都有时间的性能问题,但是有些步骤损耗的时间非常短暂,如果我们花费大力气去处理,但是收益却很少,显然是不合适的,因此我们就从几个大的可以影响性能的方向入手。

3.1 main 函数之前

从上述的启动过程中可以看到,影响的因素很多,大体上可以分为:
<1>动态库的数量。
<2>OC类的数量,category的数量。
<3>C的constructor函数越多,启动越慢。
<4>C++静态对象越多,启动越慢。
<5>OC类的+load方法。

3.2 main 函数之后

main函数之后,我们就是会执行Appdelegate中的代理以及首页页面的渲染,因此这一部分的影响因素,大体上可以分为:
<1>执行main函数的耗时。
<2>执行Appdelegate中的applicationWillFinishLaunching的耗时。
<3>APP首页的渲染耗时。

四:APP执行时间

4.1 main函数之前

我们可以通过xcode的方法,在Project→Scheme→Edit Scheme中通过设置参数,然后可以在控制台打印对应的时间日志。


xcode运行,我们可以得到准确的数据。

iPhoneX
Total pre-main time: 770.45 milliseconds (100.0%)
         dylib loading time: 150.62 milliseconds (19.5%)
        rebase/binding time:   6.02 milliseconds (0.7%)
            ObjC setup time:   9.22 milliseconds (1.1%)
           initializer time: 604.57 milliseconds (78.4%)
           slowest intializers :
    libMainThreadChecker.dylib :  45.22 milliseconds (5.8%)
          libglInterpose.dylib : 428.37 milliseconds (55.6%)
                   PuddingPlus : 189.86 milliseconds (24.6%)

从上面可以看出,整个过程耗时770ms,其中我们分析,动态库的加载,initializer和libglInterpose比较耗时,好了,我们已经知道时间在哪里浪费了,那么接下来就可以具体去一一对应解决了。

<1>加载动态库部分
进行代码瘦身,code清理,同时清理无用的资源和代码,毕竟APP体积增大也会拖慢启动速度,排查项目中引用的动态库,及时删除无用的资源。
<2>initializer 部分
排查项目中+load方法的使用,规避一些在APP启动时的非必要操作,可以延迟到冷启动之后的某一个时间点来做。
<3>libglInterpose
据我查找,这个是Xcode在debug时会加载的一个动态库,本次不做深究。

4.2 main函数之后

时间我们可以通过main函数入口以及Appdelegate打印具体的时间。
<1>main函数执行,如果里面没有主动加入任何耗时的任务,可以直接跳过。
<2>Appdelegate中的didFinishLaunchingWithOptions方法的优化
大部分APP在此方法中都会去实例化一些APP启动必须的类,或者注册一些监听,因此我们需要排查分析,是否每一个注册都是必须的,根绝我们的业务需要,适当的延后处理可不可以,比如APP的log日志部分,对于需要上传的日志部分,我们完全可以等到APP启动完成之后再做处理。
<3>首页渲染
*可以采取懒加载的方式,尽量延后执行我们所有类的实例化。
*采用facebook的模式,预先加载无数据页面,等待网络请求返回数据在刷新页面。
*采用缓存策略,预加载数据,减少用户等待的时间。

五:结语

对于快速迭代的App,随着业务复杂度的增加,冷启动时长会不可避免的增加。因此一次简单的优化是不行的,只能通过不断的努力,来不断的突破,同时还需要不要的监控日志平台分析数据,有效的业务管理,已经合理的设计,才能保证我们APP的良好体验。

你可能感兴趣的:(iOS 启动过程分析及优化)