iOS启动相关的总结
移动应用的启动时间是影响用户体验的一个重要方面,iOS的启动分为冷启动和热启动
- 冷启动: 应用尚未运行,必须加载并构建整个应用,完成初始化的工作
- 热启动: 应用已经在后台运行(常见的场景是用户按了 Home 按钮),由于某个事件将应用唤醒到前台
- 冷启动往往比热启动耗时长,而且每个应用的冷启动耗时差别也很大,所以冷启动存在很大的优化空间,冷启动时间从
applicationDidFinishLaunching:withOptions:
方法开始计算,很多应用会在该方法对其使用的第三方库初始化 - 热启动应用会在
applicationWillEnterForeground:
方法接收应用进入前台的事件
冷启动
冷启动里面存在很多资源密集型的操作,下面是苹果官方文档给的应用的启动时序图
启动顺序如下:
- 用户点击APP ICON
- 调用main函数
- 调用UIApplicationMain函数
- 加载UI文件
- 回调willFinishLaunchingWithOptions
- 存储UI状态
- 完成didFinishLaunchingWithOptions回调
- 进入Runloop循环
在调用main函数之前,系统还帮我们做了很多操作,主要是dyld这个动态链接器在负责,核心流程如下
- 程序执行从_dyld_star开始
- 读取macho文件信息,设置虚拟地址偏移量,用于重定向。
- 调用dyld::_main方法进入macho文件的主程序
- 配置一些环境变量
- 设置的环境变量方便我们打印出更多的信息。
- 调用getHostInfo()来获取machO头部获取当前运行架构的信息。
- 实例化主程序,即macho可执行文件。
- 加载共享缓存库。
- 插入动态缓存库。
- 链接主程序。
- 初始化函数。
- 经过一系列的初始化函数最终调用notifSingle函数。
- 此回调是被运行时_objc_init初始化时赋值的一个函数load_images
- load_images里面执行call_load_methods函数,循环调用所用类以及分类的load方法。
- doModInitFunctions函数,内部会调用全局C++对象的构造函数,即_ _ attribute_ _((constructor))这样的函数。
- 返回主程序的入口函数,开始进入主程序的main()函数。
PP启动的总时间) = t1(main函数之前的时间) + t2(main函数之后的时间)
- t1 = 系统的 dylib (动态链接库)和 App 可执行文件的加载时间
- t2 = main函数执行之后到 AppDelegate 类中的applicationDidFinishLaunching:withOptions:方法执行结束前这段时间
怎么计算t2
因为Class的load
方法在main函数执行之前调用,所以我们可以在load
方法记录开始时间,同时监听UIApplicationDidFinishLaunchingNotification
通知,收到通知时将时间相减作为应用启动时间,这样做有一个好处,不需要侵入到业务方的main函数去记录开始时间点
static uint64_t loadTime;
static uint64_t applicationRespondedTime = -1;
static mach_timebase_info_data_t timebaseInfo;
static inline NSTimeInterval MachTimeToSeconds(uint64_t machTime) {
return ((machTime / 1e9) * timebaseInfo.numer) / timebaseInfo.denom;
}
@implementation XXStartupMeasurer
+ (void)load {
loadTime = mach_absolute_time();
mach_timebase_info(&timebaseInfo);
@autoreleasepool {
__block id<NSObject> obs;
obs = [[NSNotificationCenter defaultCenter] addObserverForName:UIApplicationDidFinishLaunchingNotification
object:nil queue:nil
usingBlock:^(NSNotification *note) {
dispatch_async(dispatch_get_main_queue(), ^{
applicationRespondedTime = mach_absolute_time();
NSLog(@"StartupMeasurer: it took %f seconds until the app could respond to user interaction.", MachTimeToSeconds(applicationRespondedTime - loadTime));
});
[[NSNotificationCenter defaultCenter] removeObserver:obs];
}];
}
}
mach_absolute_time()
来计算时间,这个一般很少用,他表示 CPU 的时钟周期数(ticks),精确度可以达到纳秒(ns),mach_absolute_time()
不受系统时间影响,只受设备重启和休眠行为影响