iOS底层原理 启动优化 图片优化 二进制重排

3 阅读6分钟

iOS 启动优化

一、基础概念:App启动两大类型

  1. 冷启动:App进程不存在,系统重新创建进程,从0到首页渲染完成(优化重点)
  2. 热启动:App进程还在后台,只是被挂起,直接唤醒,耗时很短,一般不做重点优化

启动时间定义标准(苹果):点击图标 → 首页第一帧渲染完成

二、冷启动完整流程(必考)

1. Pre-main(main函数之前,dyld加载阶段)

  1. 系统创建进程,分配虚拟内存
  2. dyld(动态链接器)启动,加载App可执行文件Mach-O
  3. Rebase:修正ASLR随机偏移,修复指针地址
  4. Bind:符号绑定,外部符号(系统库、动态库)地址填充
  5. 加载所有依赖dylib(系统库+自定义动态库)
  6. 执行+load方法、C++全局构造函数、attribute((constructor))函数
  7. 执行main函数

Pre-main阶段耗时大头:dyld加载大量动态库、过多+load、构造函数执行。

2. Post-main(main之后,到首帧渲染)

main() → UIApplicationMain → AppDelegate → 初始化根VC → View加载、布局、渲染 → 首帧
  • main:创建UIApplication、AppDelegate
  • AppDelegate回调:didFinishLaunchingWithOptions(最容易写重业务代码)
  • 创建window、rootViewController
  • VC生命周期:loadView → viewDidLoad → viewWillAppear
  • AutoLayout布局、图层渲染、图片解码 → 屏幕显示第一帧

Post-main大头:didFinishLaunching内同步业务、大量对象创建、图片解码、主线程阻塞。

三、优化方向:Pre-main 优化(main之前)

1. 减少动态库 dylib

  • 尽量合并自己的多个自定义动态库,减少dylib数量,dyld加载是线性加载,库越多越慢

  • 避免懒加载无用库,只保留运行必须的库

    系统库不用动,重点优化业务自定义动态库。

2. 减少 +load 方法(高频考点)

+load 在dyld阶段自动调用,阻塞Pre-main

  • 用+initialize替代(类第一次使用才触发,懒加载)
  • 使用__attribute__((constructor))自定义构造函数也要控制数量
  • 不要在+load里面做耗时操作:网络、文件IO、大对象初始化

3. 减少Mach-O符号量

  • 移除无用代码、无用类(Dead Code剥离)
  • 去掉无用Category,Category也会增加符号,拖慢bind
  • 编译优化:打开Dead Code Stripping,删除未使用代码

4. 编译器选项优化

  • 开启-Os Release优化(体积、加载速度)
  • 关闭不需要的调试符号

四、优化方向:Post-main 优化(main之后,业务层,工程最常用)

1. didFinishLaunchingWithOptions 减负(重中之重)

❌ 禁止在里面同步执行:

  • 第三方SDK初始化(埋点、推送、统计、直播SDK)
  • 本地大文件读写、数据库操作
  • 大量对象初始化、复杂计算

✅ 优化方案:

  1. 区分【必须启动前初始化】和【可以延迟初始化】

    • 必须:推送核心、Crash监控(崩溃收集要尽早)
    • 其余全部丢到异步子线程或者首帧渲染后延迟执行
  2. 延迟调度:dispatch_after / 监听mainRunLoop空闲时机(CATransaction / RunLoop空闲任务)

  3. SDK懒加载:用到的时候再初始化,不要启动一次性全部初始化

坑:子线程初始化SDK,但SDK内部操作UI,会crash;SDK初始化要确认线程安全。

2. 首页VC优化

  1. viewDidLoad / viewWillAppear 不要做重任务
  2. 图片延迟加载:大图不要一次性全部解码,放到子线程解码,首屏只加载可视区域图片
  3. 减少首屏控件数量,减少AutoLayout约束,复杂布局用frame降低布局开销
  4. 避免在主线程做数据库查询、大量计算

五、其他优化点

  1. 二进制重排(非常重要,大厂标配)

    • 原理:PageFault缺页中断。启动用到的函数分散在Mach-O不同页,触发大量缺页中断,读磁盘慢。
    • 二进制重排:把启动阶段调用的函数全部排列到Mach-O文件前面连续内存页,减少PageFault,降低IO耗时。
    • 工具:Clang SanitizerCoverage收集启动函数trace,生成order文件。
  2. 资源优化

    • 图片:压缩图片、使用WebP,大图按需加载,避免启动一次性加载大量图片
    • 减少启动阶段plist、json大文件读取
    • 无用资源删除(Asset、本地化资源)
  3. 冷启动监控(衡量优化效果)

  • 系统工具:Instruments → App Launch 专门看启动耗时,区分Pre-main、Post-main

  • 埋点:自己埋点记录main开始、首帧渲染完成时间,线上采集真实用户数据

    Instruments的App Launch模板会自动拆分dyld各个阶段耗时,直接看瓶颈。

六、面试高频追问

Q:+load 和 +initialize区别?

  1. +load:app加载类的时候就调用,dyld阶段,不管类有没有使用;父类先于子类,类先于category;阻塞启动。
  2. +initialize:类第一次收到消息时懒加载调用,只调用一次,懒触发,不阻塞Pre-main。

Q:二进制重排原理是什么?PageFault?

操作系统内存分页,程序不会一次性全部加载进内存;访问不在内存的函数时触发PageFault,磁盘读入内存,IO很慢。二进制重排让启动需要的代码集中在连续页面,减少缺页次数,减少磁盘IO。

Q:didFinishLaunching放子线程有什么坑?

  1. UI操作不能放子线程;
  2. 部分SDK内部依赖主线程RunLoop;
  3. 任务如果依赖顺序,要处理依赖关系;
  4. 任务执行完前用户操作页面,注意空状态保护。

Q:冷启动优化优先级排序(背这个)

  1. 二进制重排(PageFault,收益很大)
  2. Pre-main:减少动态库、干掉无用+load
  3. Post-main:didFinishLaunching减负,非必要SDK延迟初始化
  4. 首页VC、图片、布局优化
  5. 资源瘦身

七、简单一句话总结面试口述版

iOS冷启动分为Pre-main和Post-main两个阶段。Pre-main是dyld加载Mach-O、绑定符号、执行load;Post-main是main函数到首帧渲染。优化优先减少动态库、移除+load、二进制重排减少缺页中断;业务侧把didFinishLaunching里面非必要任务异步延迟,首页减少主线程计算和图片解码,最后用Instruments App Launch模板定位瓶颈。


图片优化

区分两套东西:OpenGL ES / Metal

  1. OpenGL ES(老 API,PowerVR SGX,iPhone4S/5) MAX_TEXTURE_SIZE = 4096 × 4096,超过这个尺寸创建纹理直接失败,无法上传 GPU。 iPhone4 及更早:2048×2048 上限。

  2. Metal(A7 芯片及以后,iPhone5s+)

    • A7~A10:最大纹理 8192 × 8192
    • A11 及更新(iPhone X 以后):16384 ×16384

二进制重排

待定,目前没时间。