iOS启动总结

1,734 阅读3分钟

iOS启动相关的总结

移动应用的启动时间是影响用户体验的一个重要方面,iOS的启动分为冷启动和热启动

  • 冷启动: 应用尚未运行,必须加载并构建整个应用,完成初始化的工作
  • 热启动: 应用已经在后台运行(常见的场景是用户按了 Home 按钮),由于某个事件将应用唤醒到前台
  1. 冷启动往往比热启动耗时长,而且每个应用的冷启动耗时差别也很大,所以冷启动存在很大的优化空间,冷启动时间从applicationDidFinishLaunching:withOptions:方法开始计算,很多应用会在该方法对其使用的第三方库初始化
  2. 热启动应用会在 applicationWillEnterForeground: 方法接收应用进入前台的事件

冷启动

冷启动里面存在很多资源密集型的操作,下面是苹果官方文档给的应用的启动时序图

image.png

启动顺序如下:

  • 用户点击APP ICON
  • 调用main函数
  • 调用UIApplicationMain函数
  • 加载UI文件
  • 回调willFinishLaunchingWithOptions
  • 存储UI状态
  • 完成didFinishLaunchingWithOptions回调
  • 进入Runloop循环

在调用main函数之前,系统还帮我们做了很多操作,主要是dyld这个动态链接器在负责,核心流程如下

  1. 程序执行从_dyld_star开始
    • 读取macho文件信息,设置虚拟地址偏移量,用于重定向。
    • 调用dyld::_main方法进入macho文件的主程序
  2. 配置一些环境变量
    • 设置的环境变量方便我们打印出更多的信息。
    • 调用getHostInfo()来获取machO头部获取当前运行架构的信息。
  3. 实例化主程序,即macho可执行文件。
  4. 加载共享缓存库。
  5. 插入动态缓存库。
  6. 链接主程序。
  7. 初始化函数。
    • 经过一系列的初始化函数最终调用notifSingle函数。
    • 此回调是被运行时_objc_init初始化时赋值的一个函数load_images
    • load_images里面执行call_load_methods函数,循环调用所用类以及分类的load方法。
    • doModInitFunctions函数,内部会调用全局C++对象的构造函数,即_ _ attribute_ _((constructor))这样的函数。
  8. 返回主程序的入口函数,开始进入主程序的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()不受系统时间影响,只受设备重启和休眠行为影响