App启动优化
我们在使用某些超级App时,第一感受就是这款App怎么这么长时间才启动起来,等待的时间过长可能我们自己就把App杀掉了,有的甚至可能没有第二次启动机会就被卸载掉了,这个启动时间是非常影响用户体验的。虽然我现在开发的App还不是超级App,启动时间也说不上很慢,但是毕竟启动时间对于用户来说是越短越好,本着这个目标,我希望这次的优化能够更好的优化App启动时间,给用户带来更好的用户体验。
1、App启动类型
iPhone App的启动可以分为两种类型:冷启动和热启动。
1.1 冷启动
App启动前,其进程已被杀死或根本就没有在系统中存在,这种情况下点击App图标启动,就是一次冷启动过程。
1.2 热启动
App启动后,将App切到后台,但是进程没有别杀死,进程还在系统中存在,这种情况下重新点击App图标进入App,就是一次热启动过程。
针对上述两种启动,我们一般优化是指的优化第一种类型的启动过程。
2、App启动过程
冷启动的过程就是从用户点击App图标开始,一直到- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
方法执行完成为止。整个过程可以分为两个阶段,main()
函数执行之前和main()
函数执行之后。
2.1 main()函数执行之前
- 加载可执行文件(App的.o文件集合)
- 加载动态链接库,进行rebase指针调整和bind符号绑定
- Objc运行时的初始处理,包括Objc相关类的注册、Category注册、selector唯一性检查等
- 初始化,包括执行+load()方法、attribute((constructor))修饰的函数调用、创建C++静态全局变量
备注:
- rebase指针调整:修复指向当前镜像内部的资源指针。
- binding符号绑定:指向镜像外部的资源指针
- image 二进制文件,包括可执行文件或so文件,里边是被编译过的符号、代码等。
- imageLoader 将image加载进内存,且每一个文件对应一个ImageLoader实例来负责加载。
dyld(动态连接器,the dynamic link editor)是一个专门用来加载动态链接库的库,dyld从可执行文件的依赖开始,递归加载所有依赖的动态链接库。动态链接库包括:iOS中用到的所有系统framework,加载OC runtime方法的libobjc,系统级别的libSystem
相应的,这个阶段对于启动速度优化来说,可以优化的点包含以下几个方面:
- 减少动态库加载。苹果公司建议使用更少的动态库,如果项目使用的动态库较多,可以将多个动态库合并使用,最多支持6个非系统动态库合为一个。
- 减少objc类和selector数量,删除启动后不会使用的类或者方法,合并分类文件,删除无用依赖库。
- 将不必在+load()方法中做的事情延迟到+initialize()方法中。
- 删减无用静态变量,控制C++全局变量的数量。
2.2 main()函数执行之后
这个阶段是指从main()函数执行开始,到Appdelegate的application:didFinishLaunchingWithOptions:
方法执行完成。App的启动逻辑、首屏渲染和业务代码都是在这个阶段,主要包括:
- 全局初始化配置,各种组件库的初始化(crash统计、埋点统计)
- 首页数据请求、获取和处理
- 首页UI计算和渲染
对于第一条,我们要梳理出哪些配置和初始化工作是首屏渲染必要的,哪些是App启动所要求的,除此之外的配置和初始化都分别滞后到合适的阶段执行即可。
优化点:
- 梳理首屏渲染所不必须的依赖库和功能逻辑,做延迟加载处理,比如放到首页的
- (void)viewDidAppear:(BOOL)animated
方法中去执行。 - 在首页控制器的
- (void)viewDidLoad
和- (void)viewWillAppear:(BOOL)animated
尽量的减少要做的事情,使首页能够尽快的加载显示出来。 - 不使用
storyboard
或xib
构建首页UI视图,使用纯代码来加载首页。
3、App启动优化
说了这么多,App启动优化的时间如何来衡量呢,这就要用到上面提到的时间T1和T2,下面我们来说下如何来计算T1和T2。
测试使用机器:iPhone 7 Plus,iOS 10.3.2系统,存储容量32GB。
3.1 T1
main()
之前的时间T1苹果官方提供了一种方法,在Xcode的Edit Scheme...-->Run-->Arguments
选项中设置Environment Variables,添加name为DYLD_PRINT_STATISTICS
,Value为1
。
项目使用真机运行,在控制台就可以看到如下log了。
Total pre-main time: 4.2 seconds (100.0%)
dylib loading time: 1.9 seconds (46.9%)
rebase/binding time: 1.6 seconds (39.3%)
ObjC setup time: 81.78 milliseconds (1.9%)
initializer time: 498.71 milliseconds (11.7%)
slowest intializers :
libSystem.B.dylib : 19.39 milliseconds (0.4%)
AppName : 918.63 milliseconds (21.6%)
可以看到Total pre-main time
总耗时为4.2秒。
3.2 T2
main()
之后到- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
是T2阶段的时间,我们可以采用记住头尾时间取差值的方式得到T2,main函数代码如下:
extern CFAbsoluteTime startTime;
bool isBeauifulStr(char *str);
int main(int argc, char * argv[]) {
startTime = CFAbsoluteTimeGetCurrent();
@autoreleasepool {
return UIApplicationMain(argc, argv, nil, NSStringFromClass([MIAppDelegate class]));
}
}
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
方法中在最后加入如下代码:
CFAbsoluteTime startTime;
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
...
dispatch_async(dispatch_get_main_queue(), ^{
NSUInteger ms = (NSUInteger)((CFAbsoluteTimeGetCurrent() - startTime) * 1000);
NSLog(@"T2 = %lu ms", ms);
});
return YES;
}
项目启动后,输出结果
T2 = 1190 ms
参考上面的优化点逐条去对启动过程进行优化,然后对比得到的T1和T2的时间,就可以知道你的优化效果到底如何了。
参考
iOS 程序 main 函数之前发生了什么
今日头条iOS客户端启动速度优化
App 启动速度怎么做优化与监控?
优化App的启动时间