iOS 点击app icon到首页经历的整个过程

0 阅读5分钟

用户点击图标 → 系统预处理 → 应用初始化 → UI 渲染显示 下面是这个过程的详细流程图,帮助你直观把握每个关键步骤:

image.png

 详细步骤解析

第一阶段:系统预处理(SpringBoard 与内核)​

  1. 图标点击识别

    • 你点击图标时,实际上点击的是 ​SpringBoard​(iOS 的主屏幕应用)。
    • SpringBoard 捕获点击事件,识别对应的 App Bundle ID。
  2. 进程创建与沙盒检查

    • 系统内核为 App 创建新的进程,分配独立的虚拟内存空间。
    • 启用 ​Sandbox​ 沙盒机制,限制应用的文件系统访问权限。
    • 检查代码签名,确保应用未被篡改。
  3. 加载 Mach-O 可执行文件

    • 从应用的 .app 包中加载 ​Mach-O 文件​(可执行文件格式)。

    • 加载过程包括:

      • 加载程序​:读取 Mach-O 头部,检查 CPU 架构兼容性。
      • 分配地址空间​:为代码段(__TEXT)、数据段(__DATA)等分配内存。
      • 代码签名验证​:确保所有代码页的完整性。
  4. 动态链接(dyld)​

    • 动态链接器(dyld)​​ 开始工作,这是启动耗时的大头之一。
    • dyld 递归加载所有依赖的动态库​(UIKit、Foundation 等系统库)。
    • 执行符号绑定重定位,将库中的函数地址与调用点关联。
    • iOS 13 后引入了 ​dyld 3,通过启动闭包缓存大幅优化了这一过程。

第二阶段:应用初始化(main 函数之后)​

  1. 执行 main() 函数

    objc
    复制
    // 这是 Objective-C 应用的起点
    int main(int argc, char * argv[]) {
        @autoreleasepool {
            return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
        }
    }
    
    • 如果使用 Swift,@main 标注的 App 结构体是入口,但底层仍会调用相同的初始化流程。
  2. ​+load 方法调用

    • 在 main() 之前,所有类和分类的 ​**+load**​ 方法会被调用。
    • 按继承关系顺序执行,这是最早执行自定义代码的地方(但 Apple 建议尽量少用)。
  3. UIApplication 初始化

    • UIApplicationMain() 创建 ​UIApplication 单例​ 和 ​AppDelegate​ 实例。
    • 建立应用的主运行循环(Main RunLoop)​,这是所有事件处理的基础。
  4. 生命周期回调

    • 调用 application:didFinishLaunchingWithOptions: 这是你编写启动代码的主要位置。

    • 这里常见的操作包括:

      • 初始化第三方 SDK
      • 配置窗口(UIWindow
      • 设置根视图控制器
      • 读取持久化配置(UserDefaults)

第三阶段:UI 渲染与显示

  1. 视图控制器初始化

    • 根视图控制器的 ​**init​ 或 ​initWithCoder:**​(如果来自故事板)被调用。
    • 如果使用故事板,还会通过 ​**UIStoryboard**​ 反序列化视图层次。
  2. 视图加载周期

    • ​**loadView**​:创建或从 nib 加载视图(如果未从故事板加载)。
    • ​**viewDidLoad**​:视图被加载到内存,这是配置子视图、数据绑定的关键时机。
    • 此时视图尚未确定最终尺寸和位置。
  3. 视图布局与显示

    • ​**viewWillAppear:**​:视图即将显示,可执行最后的状态更新。
    • ​**viewWillLayoutSubviews**​:系统开始计算自动布局约束。
    • ​**viewDidLayoutSubviews**​:布局完成,视图获得最终 frame,适合基于 frame 的调整。
    • 如果使用了 Auto Layout,系统会在此计算所有约束,可能需要多次迭代。
  4. 绘制与渲染

    • 系统调用 ​**drawRect:**​(如有自定义绘制)生成视图内容。
    • Core Animation 开始图层树的提交和渲染(参考之前的渲染管线)。
    • 图片解码、文本绘制等 CPU 工作在主线程执行。
    • GPU 接收处理后的数据,进行光栅化、纹理合成。
  5. 首次提交与显示

    • 渲染完成的图层树通过 ​Render Server​ 提交给 GPU。
    • GPU 渲染的帧在下一个 ​VSync​ 信号到达时,显示到屏幕。
    • 调用 ​**viewDidAppear:**,标志首页已完全显示。

⚡ 启动优化关键点(面试加分项)

在解释完整流程后,你可以主动提到优化策略,展示深度:

  1. pre-main 阶段优化

    • 减少动态库数量,合并多个动态库。
    • 使用静态库替代动态库(权衡包大小)。
    • 控制 +load 方法的使用,尽量移到 +initialize 中。
  2. main 阶段优化

    • 在 didFinishLaunchingWithOptions 中:

      • 将非必须的初始化延后(通过 dispatch_async 或首屏显示后执行)。
      • 使用 ​启动故事板​ 而非静态图片,系统会自动复用故事板作为初始界面,减少白屏时间。
      • 避免在主线程进行繁重的 I/O、网络请求或复杂计算。
  3. 首屏渲染优化

    • 简化首页视图层次,减少不必要的视图嵌套。
    • 对于复杂但静态的视图,考虑使用 ​**shouldRasterize**​ 进行栅格化缓存。
    • 使用正确的图片尺寸,避免运行时缩放带来的性能开销。
  4. 测量工具

    • 使用 Xcode 的 ​Metrics Organizer​ 查看启动耗时。
    • 通过添加环境变量 ​**DYLD_PRINT_STATISTICS**​ 获取详细的 dyld 加载时间。
    • 用 Instruments 的 ​App Launch​ 模板分析启动各阶段耗时。

💎 面试回答技巧

结构化表达​:

“整个过程可以分为三个主要阶段:首先是系统层面的预处理,包括进程创建、沙盒验证和动态链接;然后是应用层面的初始化,从 main 函数到 AppDelegate 回调;最后是 UI 层的渲染显示,涉及视图控制器生命周期和 Core Animation 渲染管线。”

关联用户体验​:

“从用户体验角度,这就是我们常说的‘冷启动时间’。Apple 建议控制在 400 毫秒以内,否则用户会感知到延迟。优化启动时间需要针对每个阶段的具体瓶颈进行分析。”

展现深度​:

“在之前项目中,我们通过分析 DYLD_PRINT_STATISTICS 的输出,发现动态库加载是瓶颈。通过将多个内部动态库合并,并将部分非必需的初始化延迟到首屏显示之后,成功将启动时间降低了 30%。”

准备追问​:

准备好回答可能的追问:

  • “热启动和冷启动有什么区别?”(热启动时,dyld 3 的启动闭包已缓存,跳过了很多重复工作)
  • “如何测量启动时间?”(使用 CFAbsoluteTimeGetCurrent() 记录时间点,或使用 Instruments)
  • “启动故事板为什么比静态启动图好?”(系统会将启动故事板编译为 SBS 文件,直接用作初始界面,避免闪白)