App启动分析与优化策略

一、 启动过程分析

1、解析Info.plist

a.加载相关信息,例如闪屏

b.沙箱建立、权限检查

2、Mach-O加载

Mach-O 文件:我们写的程序想要跑起来,肯定它的可执行文件格式要被操作系统所理解。比如ELF是Linux下的可执行文件格式,那么对于OS X / iOS来说,Mach-O 是其可执行文件格式。

Mach-O格式主要包括以下几种文件类型:

Executable:应用的主要二进制

Dylib:动态链接库

Bundle:不能被链接,只能在运行时使用dlopen加载

Image:包含Executable、Dylib和Bundle

Framework:包含Dylib、资源文件和头文件的文件夹

如果是胖二进制文件,寻找合适当前CPU类别的部分

加载所有依赖的Mach-O文件(递归调用Mach-O加载的方法)

定位内部、外部指针引用,例如字符串、函数等

执行声明为__attribute__((constructor))的C函数

加载类扩展(Category)中的方法

C++静态对象加载、调用ObjC的 +load 函数

3、程序执行

调用main()

调用UIApplicationMain()

调用applicationWillFinishLaunching

二、测量App的启动时间

iOS App的启动有两种方式:冷启动和热启动,下面对这两种启动方式进行简单介绍。

冷启动:App第一次启动或者是被上滑Kill掉之后,再次从点击App应用图标到App完全启动显示主页面为止的过程成为冷启动。

热启动:当用户从当前App按下HOME键退出后,系统并不会立即kill掉App的进程,还会继续后台执行一段时间(至于什么时候会被kill,完全取决于系统内存等资源的使用情况,理想情况下可以保持后台运行15min)。然后当用户再次点击App图标时,会很快加载到上次停留的页面,几乎不需要进行资源载入,这种情况下的启动成为热启动。

下面我们对于App启动时间的测量只讨论在冷启动情形下。根据苹果官方介绍,iOS应用的启动分为两个阶段:pre-main和main,所以App启动的总时间为:Total Time = pre-main Time + main Time。

Pre-main 阶段

Pre-mian 阶段即main()函数被调用之前的加载时间,包括dylib动态库的加载、Mach-O文件加载、Rebase/Bining、Objective-C Runtime 加载等。该过程如下图所示:

main 阶段

main 阶段指的是从调用main()函数开始,调用UIApplicationMain()创建UIApplication,AppDelegate,直到回调App代理方法applicationDidBecomeActive:为止,其中包括application:didFinishLaunchingWithOptions方法中创建keyWindow、三方sdk和其他初始化项所消耗的时间,也包含RootViewController 的 ChildViewController的view完全显示出来所花费的时间。该过程如下图所示:

main 阶段时间测量

那么对于pre-main 和 main 启动阶段的耗时如何测量呢?苹果已为我们提供了一个简单易用的测试方法:在Xcode中选中项目的Scheme→ Edit Scheme...→,然后选择Run→Arguments→Environment Variables中添加Name = DYLD_PRINT_STATISTICSvalue,Value = 1 的环境变量。如下图所示:

然后重新运行项目,注意控制台下面的打印输出,格式为:

Totalpre-main time:655.68milliseconds (100.0%)

dylib loading time:205.67milliseconds (31.3%)

rebase/binding time:320.95milliseconds (48.9%)

ObjCsetup time:63.07milliseconds (9.6%)

initializer time:65.85milliseconds (10.0%)

          slowest intializers :

libSystem.B.dylib :3.43milliseconds (0.5%)

libMainThreadChecker.dylib :15.59milliseconds (2.3%)

century :76.56milliseconds (11.6%)


Totalpre-main time:  表示pre-main阶段总的时间消耗为655.68毫秒

dylib loading time: 动态库加载时间

rebase/binding time: 重定位指针指向/指向镜像外部内容 所需时间

ObjCsetup time:ObjC初始化时间

initializer time: 其他初始化时间的总和

slowest intializers : 最慢的三个初始化分别是libSystem.B.dylib、libMainThreadChecker.dylib、century(项目名称)

main 阶段时间测量

由于main阶段涉及到大量的自定义代码,app主体页面的初始化、前期配置、三方库初始化等大量逻辑代理,iOS系统没有直接的测量方式,所以只能通过打点的方式计算启动开始到启动结束的时间差即可。例如下面示例代码:

// main.m 文件

#import"AppDelegate.h"

externCFAbsoluteTimeStartTime;

intmain(intargc,char* argv[]) {

// 记录开始时间

StartTime =CFAbsoluteTimeGetCurrent();

@autoreleasepool{

returnUIApplicationMain(argc, argv,nil,NSStringFromClass([AppDelegateclass]));

    }

}

// AppDelegate.m 文件

CFAbsoluteTimeStartTime;

- (void)applicationDidBecomeActive:(UIApplication*)application {

dispatch_async(dispatch_get_main_queue(), ^{

NSUIntegermilliseconds = (NSUInteger)((CFAbsoluteTimeGetCurrent() - StartTime) *1000);

NSLog(@"Loading done in %lu ms", milliseconds);

    });

}


影响启动性能的因素

接下来我们讨论在App启动的pre-main阶段和mian阶段,影响启动性能的多种因素。

Pre-main 阶段

动态库加载越多,启动越慢。

ObjC类越多,启动越慢

C的constructor函数越多,启动越慢

C++静态对象越多,启动越慢

ObjC的+load方法使用越多,启动越慢

我们尽量不要写__attribute__((constructor))的C函数,也尽量不要用到C++的静态对象;至于ObjC的+load方法,似乎大家已经习惯不用它了。任何情况下,能用dispatch_once()来完成的,就尽量不要用到以上的方法。

main 阶段

执行main()函数的耗时

执行application:didFinishLaunchingWithOptions的耗时

rootViewController及其childViewController的加载、view及其subviews的加载

一般的App的主体UI架构是,UITabbarViewController 作为 keyWindow的根控制器,然后包含多个ChildViewController作为每个tab的根控制器。然而在didFinishLaunchingWithOptions代理方法中各控制器的初始化顺序是怎样的呢?

答案是:

-[MQQTabBarController viewDidLoad]

-[MQQTab1ViewController viewDidLoad]

-[AppDelegate application:didFinishLaunchingWithOptions:]

-[MQQTab2ViewController viewDidLoad] (点击了第二个tab之后加载)

-[MQQTab3ViewController viewDidLoad] (点击了第三个tab之后加载)

从上面的加载过程可以看出,在main()方法之后的启动中,只要是对页面的渲染,要减少这个阶段的耗时需要在viewDidLoad方法中做优化,减少不必要的逻辑,视图尽量使用懒加载,使用异步方式加载网络数据。

三、启动时间优化

苹果建议在400ms内完成pre-main 阶段的启动,App整体的启动时间不能超过20s,否则系统会被kill掉进程。

由于每个App的体量和类型不一样,对应启动过程中配置的逻辑处理复杂程度也是皆不相同,所有没有一个标准的时间来衡量启动时间是否达到最优。但是可以从用户体验上下功夫预加载占位视图,数据缓存等都可以营造出加载速度提升的感觉。

下面从实践的角度分析可优化的部分:

pre-main 阶段优化

1、移除不需要的动态库framework

2、移除不需要的类、未使用的变量、方法等

对应项目中大量的文件,可以使用工具快速扫描整个项目找出未使用的类、变量、或方法。这里推荐一个开源工具 Fui 能准确的完成此任务,不足之处在于它处理不了动态库或静态库中提供的类,和C++ 的类模板。

Fui使用方法:

在终端中cd到项目根目录,然后执行fui find,就可以得到一个列表,可根据这个列表在Xcode中手动检查并删除无用代码。

3、合并功能类似的类和扩展(Category)

4、+load方法中做的事情可以使用dispatch_once()来代替,因为main()方法被调用之前就会加载+load方法,会影响pre-main阶段启动事件。

5、压缩资源图片(对要求不高的图片可做压缩处理)

main 阶段优化

1、优化application: didFinishLaunchingWithOptions:内部逻辑

各种业务请求配置更新,多个配置更新可合并为单个请求,部分业务的配置延后更新。

对于新版本引导、广告闪屏逻辑,需要重构逻辑清晰

数据迁移,对太旧的无用的逻辑可以删除,不需要立即处理的数据,改为后台加载

2、优化rootViewController加载:在启动过程中只加载tabbarVc,主Vc即可,而且主Vc中的ViewDidLoad方法中也只加载需要立即显示出来的view,其他视图均使用懒加载,数据进行异步加载。


文章转载:https://www.dazhuanlan.com/2020/01/04/5e1055a4e07fa/?__cf_chl_jschl_tk__=7750f38bebb1f21fdaddcddca0415ee71d31c736-1614321013-0-AWpmiwwC2bq_8PHZ6dbnAI7dbzqLC36vJnCCmhi6ThuE9vMnd3LM67aZyDLR_NJL9OD6mRUWHOQyh3g4j0Pu2gfxG2bqnAUKHjusvzyCt9SgFPr_BvqiVVboKbJT2CnMKE17X9TXh29kBG4pUgxySpMoZiroz9gDZaKkCapAOGs88StJKu4rDYYjwNwCfv5apw7KDW2yUwjsu-8JFscQ3j0KlbepXj_f87G1Cq2LI0JosaCI1xKUmw1csldKhrqcDOmp-hMtRnt6tF_8dQ1ggjBaRUb0qne-6PNV4HeCmgyrGT4o2mjAJDBLu8aaGNuROH0camvmKwRQHg5_L0zQT0I

你可能感兴趣的:(App启动分析与优化策略)