目录
理论
一、App启动流程及可能导致App启动变慢的原因
二、App启动优化
实操
三、监测App启动耗时及定位导致App启动变慢的耗时代码
一、App启动流程及可能导致App启动变慢的原因
要想做App启动优化,就得先知道可能导致App启动变慢的原因,要想知道可能导致App启动变慢的原因,就得先知道App启动流程。
App启动分两种:冷启动和热启动。冷启动是指启动一个杀死的App,系统需要为App开辟一个新进程,热启动是指启动一个在后台运行的App,App的进程还存活着,系统不需要开辟新进程。而我们通常所说的App启动优化,主要是指冷启动优化——尽可能地缩短启动时间,所以接下来本文中谈到的启动都是指冷启动。
App启动流程主要分为两大阶段:main
函数调用前的阶段和main
函数调用至didFinishLaunchingWithOptions
方法执行完毕的阶段。
1、main
函数调用前的阶段
- App启动后,系统会首先加载项目的可执行文件和依赖的动态库到内存中。
- 然后系统会调用Runtime的函数对项目的可执行文件进行解析和处理,把项目中所有的类和分类加载到内存中,此时会调用项目中所有类和分类的
+load
方法。
2、main
函数调用至didFinishLaunchingWithOptions
方法执行完毕的阶段
- 系统会调用
main
函数、UIApplicationMain
函数、AppDelegate
的didFinishLaunchingWithOptions
方法,我们通常会在这个方法里给window
设置rootViewController
,不过我们要知道只有rootViewController
或者rootViewController
首屏的viewDidLoad
方法和viewWillAppear
方法走完了,才算rootViewController
设置完毕。 rootViewController
设置完毕,我们通常还会在这个方法里为App添加一些必要的启动项,didFinishLaunchingWithOptions
方法走完了就代表App启动起来了,此时系统才会去调用rootViewController
或者rootViewController
首屏的viewDidAppear
方法,这是后话了。
如果这两个阶段的环节做了太多的工作,就可能导致App启动变慢。
二、App启动优化
所以我们做App启动优化,主要就是从减少这两个阶段的环节的工作入手。
1、main
函数调用前的阶段
- 尽量减少动态库的使用,定期清理掉不必要的动态库;
- 尽量减少类和分类的数量,定期清理掉不必要的类和分类;尽量减少类和分类
+load
方法的调用,不要在里面做复杂的操作,可以用+initialize
方法和dispatch_once
替代,因为+initialize
方法是初始化类和分类时才调用的,也就是说+initialize
方法项目里确确实实用到这些类和分类时才会调用的,而+load
方法不管你用不用这些类和分类都会调用。
2、main
函数调用至didFinishLaunchingWithOptions
方法执行完毕的阶段
- 在
rootViewController
或者rootViewController
首屏的viewDidLoad
方法和viewWillAppear
方法里尽量只做一些关键数据的初始化和绘制UI操作,不要做别的耗时操作,如果非做不可,就放到子线程里去做。 - 启动项最好按需配置,不需要在App一启动就配置的启动项可以推迟到使用时再配置,必须配置的启动项如果很耗时,就放到子线程里去做。
三、监测App启动耗时
1、main
函数调用前的阶段
Xcode
--> Target
--> Edit Scheme
--> Run
--> Arguments
--> Environment Variables
,添加DYLD_PRINT_STATISTICS = YES
,400ms以内就算正常。
运行项目,控制台就会打印pre-main
阶段的启动耗时了。
我们添加一个ARKit
动态库。
我们再添加很多类和分类。
我们再调用一下某个类的+load
方法,里面休眠2s模拟耗时操作。
2、main
函数调用至didFinishLaunchingWithOptions
方法执行完毕的阶段
Xcode
--> Project
--> Target
--> Build Settings
--> dsym
--> Debug Information Format
--> 都设置为DWARF with dSYM File
,否则Time Profiler
显示出来的都是函数的内存地址而不是函数名。
Instruments
的Time Profiler
:
-
Xcode
打开项目,连接真机(一定要用真机调试,因为模拟器用的是电脑的CPU,检测不出手机的启动耗时来)。 - 打开
Instruments
的Time Profiler
。
- 选择真机、选择项目、运行、定量地监测App启动耗时,并定位耗时代码。
// AppDelegate.m
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
[self.window setBackgroundColor:[UIColor whiteColor]];
[self.window makeKeyAndVisible];
[self.window setRootViewController:[[ViewController alloc] init]];
return YES;
}
// ViewController.m
- (void)viewDidLoad {
[super viewDidLoad];
}
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
}
我们在rootViewController
的viewDidLoad
方法和viewWillAppear
方法里添加一些耗时操作。
// ViewController.m
- (void)viewDidLoad {
[super viewDidLoad];
[self doSomething];
}
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
[self doSomething];
}
#pragma mark - 模拟耗时操作
- (void)doSomething {
for (int i = 0; i < 200000; i++) {
NSLog(@"%d", i);
}
}
我们再在rootViewController
设置完毕、didFinishLaunchingWithOptions
方法执行完毕之前添加一些耗时操作。
// AppDelegate.m
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
[self.window setBackgroundColor:[UIColor whiteColor]];
[self.window makeKeyAndVisible];
[self.window setRootViewController:[[ViewController alloc] init]];
[self doSomething];
return YES;
}
#pragma mark - 模拟耗时操作
- (void)doSomething {
for (int i = 0; i < 200000; i++) {
NSLog(@"%d", i);
}
}