iOS App 的启动性能(一)

2020-11-24  本文已影响0人  番茄爱土豆me

介绍一下如何优化 iOS App 的启动性能,分为四个部分:

第一部分科普了一些和App启动性能相关的前置知识

第二部分主要讲如何定制启动性能的优化目标

第三部分通过在WiFi管家这个具体项目的优化过程,分享一些有用的经验

第四部分是关键点的总结。

【第一部分】一些小科普

因为篇幅的限制,没有办法很详尽的说明一些原理性的东西,只是方便大家了解哪些事情可能跟启动性能有关。同时,内容相对也比较入门,大神们请跳过这一部分。

1. App启动过程

解析Info.plist

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

沙箱建立、权限检查

Mach-O加载

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

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

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

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

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

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

程序执行

调用main()

调用UIApplicationMain()

调用applicationWillFinishLaunching

2. 如何测量启动过程耗时

冷启动比热启动重要

当用户按下home键的时候,iOS的App并不会马上被kill掉,还会继续存活若干时间。理想情况下,用户点击App的图标再次回来的时候,App几乎不需要做什么,就可以还原到退出前的状态,继续为用户服务。这种持续存活的情况下启动App,我们称为热启动,相对而言冷启动就是App被kill掉以后一切从头开始启动的过程。我们这里只讨论App冷启动的情况。

main()函数之前

在不越狱的情况下,以往很难精确的测量在main()函数之前的启动耗时,因而我们也往往容易忽略掉这部分数据。小型App确实不需要太过关注这部分。但如果是大型App(自定义的动态库超过50个、或编译结果二进制文件超过30MB),这部分耗时将会变得突出。所幸,苹果已经在Xcode中加入这部分的支持。

苹果提供的方法

在Xcode的菜单中选择Project→Scheme→Edit Scheme...,然后找到Run→Environment Variables→+,添加name为DYLD_PRINT_STATISTICSvalue为1的环境变量。

在Xcode运行App时,会在console中得到一个报告。例如,我在WiFi管家中加入以上设置之后,会得到这样一个报告:

Total pre-main time: 94.33milliseconds (100.0%)

         dylib loading time: 61.87milliseconds (65.5%)

        rebase/binding time:  3.09milliseconds (3.2%)

            ObjC setup time: 10.78milliseconds (11.4%)

           initializer time: 18.50milliseconds (19.6%)

           slowest intializers :

             libSystem.B.dylib:  3.59milliseconds (3.8%)

   libBacktraceRecording.dylib:  3.65milliseconds (3.8%)

                    GTFreeWifi :  7.09milliseconds (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()函数之后的部分。

3. 影响启动性能的因素

App启动过程中每一个步骤都会影响启动性能,但是有些部分所消耗的时间少之又少,另外有些部分根本无法避免,考虑到投入产出比,我们只列出我们可以优化的部分:

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的加载

applicationWillFinishLaunching的耗时

如果有这样这样的代码:

//AppDelegate.m

@implementationAppDelegate

- (BOOL)application:(UIApplication*)application didFinishLaunchingWithOptions:(NSDictionary*)launchOptions {

   self.rootViewController= [[[MQQTabBarController alloc] init] autorelease];

   self.window= [[[UIWindowalloc] init] autorelease];

    [self.windowmakeKeyAndVisible];

   self.window.rootViewController=self.rootViewController;

   UITabBarController*tabBarViewController = [[[UITabBarControlleralloc] init] autorelease];

   NSLog(@"%s", __PRETTY_FUNCTION__);   

   returnYES;

}

...

//MQQTabBarController.m

@implementationMQQTabBarController

- (void)viewDidLoad {

   NSLog(@"%s", __PRETTY_FUNCTION__);

    [superviewDidLoad];   

// Do any additional setup after loading the view.

   UIViewController*tab1 = [[[MQQTab1ViewController alloc] init] autorelease];

    tab1.tabBarItem.title=@"red";

    [selfaddChildViewController:tab1];

   UIViewController*tab2 = [[[MQQTab2ViewController alloc] init] autorelease];

    tab2.tabBarItem.title=@"blue";

    [selfaddChildViewController:tab2];

   UIViewController*tab3 = [[[MQQTab3ViewController alloc] init] autorelease];

    tab3.tabBarItem.title=@"green";

    [selfaddChildViewController:tab3];

}

...

@end

那么-[MQQTabBarController viewDidLoad]、-[AppDelegate application:didFinishLaunchingWithOptions:]、-[MQQTab1ViewController viewDidLoad]、-[MQQTab2ViewController viewDidLoad]、-[MQQTab2ViewController viewDidLoad]完成的先后顺序是怎样的呢?

答案是:

-[MQQTabBarController viewDidLoad]

-[MQQTab1ViewController viewDidLoad]

-[AppDelegate application:didFinishLaunchingWithOptions:]

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

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

一般而言,大部分情况下我们都会把界面的初始化过程放在viewDidLoad,但是这个过程会影响消耗启动的时间。特别是在类似TabBarController这种会嵌套childViewController的ViewController的情况,它也会把部分children也初始化,因此各种viewDidLoad会递归的进行。

最简单的解决的方法,是把viewController延后加载,但实际上这属于一种掩耳盗铃,确实,applicationWillFinishLaunching的耗时是降下来了,但用户体验上并没有感觉变快。

更好一点的解决方法有点类似facebook,主视图会第一时间加载,但里面的数据和界面都会延后加载,这样用户就会阶段性的获得视觉上的变化,从而在视觉体验上感觉App启动得很快。

如果有技术交流可以加我V:herodon(也可以交流一下最近学习的 马士兵 极客go 码牛)

上一篇下一篇

猜你喜欢

热点阅读