前言
当用户点击 App 图标到看到第一个界面,这短短的几百毫秒内,系统做了大量的工作。这段时间被称为 pre-main 阶段,完全由 dyld(Dynamic Loader)主导。
理解 dyld 的工作原理,是进行启动优化的基础。本文将深入剖析 dyld 的每一个步骤,并给出针对性的优化策略。
一、启动时间的构成
1.1 启动阶段划分
┌─────────────────────────────────────────────────────────────────────┐
│ App 启动时间构成 │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ 用户点击图标 │
│ │ │
│ ▼ │
│ ╔═══════════════════════════════════════════════════════════════╗ │
│ ║ T1: pre-main 阶段 (dyld 主导) ║ │
│ ║ ┌─────────────────────────────────────────────────────────┐ ║ │
│ ║ │ • 内核创建进程 │ ║ │
│ ║ │ • dyld 初始化 │ ║ │
│ ║ │ • 加载动态库 │ ║ │
│ ║ │ • Rebase & Binding │ ║ │
│ ║ │ • ObjC Runtime 初始化 │ ║ │
│ ║ │ • +load 方法和静态构造函数 │ ║ │
│ ║ └─────────────────────────────────────────────────────────┘ ║ │
│ ╚═══════════════════════════════════════════════════════════════╝ │
│ │ │
│ ▼ main() 函数开始执行 │
│ ╔═══════════════════════════════════════════════════════════════╗ │
│ ║ T2: main() 阶段 (开发者主导) ║ │
│ ║ ┌─────────────────────────────────────────────────────────┐ ║ │
│ ║ │ • main() 执行 │ ║ │
│ ║ │ • UIApplicationMain() │ ║ │
│ ║ │ • application:didFinishLaunchingWithOptions: │ ║ │
│ ║ │ • 首屏 UI 构建 │ ║ │
│ ║ └─────────────────────────────────────────────────────────┘ ║ │
│ ╚═══════════════════════════════════════════════════════════════╝ │
│ │ │
│ ▼ 首帧渲染完成 │
│ ╔═══════════════════════════════════════════════════════════════╗ │
│ ║ T3: 首帧渲染阶段 ║ │
│ ║ ┌─────────────────────────────────────────────────────────┐ ║ │
│ ║ │ • viewDidLoad │ ║ │
│ ║ │ • viewWillAppear │ ║ │
│ ║ │ • Layout & Render │ ║ │
│ ║ │ • CA::Transaction::commit() │ ║ │
│ ║ └─────────────────────────────────────────────────────────┘ ║ │
│ ╚═══════════════════════════════════════════════════════════════╝ │
│ │ │
│ ▼ │
│ 用户看到首屏内容,可以交互 │
│ │
│ ════════════════════════════════════════════════════════════════ │
│ │
│ 启动类型: │
│ • 冷启动:App 完全不在内存中,完整走上述流程 │
│ • 热启动:App 在后台被挂起,恢复执行 │
│ • 温启动:App 被终止但部分缓存还在,介于两者之间 │
│ │
└─────────────────────────────────────────────────────────────────────┘
1.2 测量启动时间
// 1. 使用环境变量测量 pre-main 时间
// Xcode → Edit Scheme → Run → Arguments → Environment Variables
// DYLD_PRINT_STATISTICS = 1
// DYLD_PRINT_STATISTICS_DETAILS = 1
/*
输出示例:
Total pre-main time: 847.25 milliseconds (100.0%)
dylib loading time: 295.52 milliseconds (34.8%)
rebase/binding time: 72.80 milliseconds (8.5%)
ObjC setup time: 89.23 milliseconds (10.5%)
initializer time: 389.15 milliseconds (45.9%)
slowest intializers :
libSystem.B.dylib : 8.89 milliseconds (1.0%)
libMainThreadChecker.dylib : 33.33 milliseconds (3.9%)
MyFramework : 156.78 milliseconds (18.5%)
MyApp : 189.45 milliseconds (22.3%)
*/
// 2. 代码中测量
CFAbsoluteTime StartTime;
int main(int argc, char * argv[]) {
StartTime = CFAbsoluteTimeGetCurrent();
@autoreleasepool {
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
}
// AppDelegate.m
- (BOOL)application:(UIApplication *)application
didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
CFAbsoluteTime launchTime = CFAbsoluteTimeGetCurrent() - StartTime;
NSLog(@"didFinishLaunching 耗时: %.2f ms", launchTime * 1000);
return YES;
}
// 首帧渲染
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
CFAbsoluteTime firstFrameTime = CFAbsoluteTimeGetCurrent() - StartTime;
NSLog(@"首帧渲染耗时: %.2f ms", firstFrameTime * 1000);
});
}
1.3 使用 Instruments 分析
┌─────────────────────────────────────────────────────────────────────┐
│ Instruments 启动分析工具 │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ 1. App Launch │
│ Xcode → Product → Profile → App Launch │
│ • 完整的启动时间线 │
│ • 各阶段耗时统计 │
│ • 方法级别的时间分析 │
│ │
│ 2. Time Profiler │
│ • 采样 CPU 使用情况 │
│ • 发现耗时函数 │
│ │
│ 3. System Trace │
│ • 系统级别追踪 │
│ • 线程调度、虚拟内存等 │
│ │
└─────────────────────────────────────────────────────────────────────┘
二、dyld 工作流程详解
2.1 dyld 演进历史
┌─────────────────────────────────────────────────────────────────────┐
│ dyld 版本演进 │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ dyld 1.0 (1996-2004) │
│ └── 最初版本,基于 NeXTSTEP │
│ │
│ dyld 2.0 (2004-2017) │
│ └── macOS 10.4+ │
│ └── 完全重写,支持预绑定 │
│ └── 引入共享缓存 (Shared Cache) │
│ │
│ dyld 3.0 (2017-至今) │
│ └── iOS 11 (部分), iOS 13+ (全面) │
│ └── 引入 Launch Closure 机制 │
│ └── 大部分工作移到 App 安装/更新时完成 │
│ └── 显著提升启动速度 │
│ │
│ dyld 4.0 (2022-至今) │
│ └── iOS 16+, macOS 13+ │
│ └── 进一步优化 Closure 机制 │
│ └── 支持 Swift 并发 │
│ │
└─────────────────────────────────────────────────────────────────────┘
2.2 dyld 2 vs dyld 3
┌─────────────────────────────────────────────────────────────────────┐
│ dyld 2 vs dyld 3 │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ 【dyld 2 - 每次启动时】 │
│ │
│ App 启动 │
│ │ │
│ ├── 解析 Mach-O Headers │
│ ├── 查找依赖库 │
│ ├── 映射所有 Mach-O 文件 │
│ ├── 执行符号查找 │
│ ├── Rebase & Binding │
│ ├── 运行 Initializers │
│ │ │
│ └── main() │
│ │
│ 问题:每次启动都要重复大量工作 │
│ │
│ ════════════════════════════════════════════════════════════════ │
│ │
│ 【dyld 3 - 分离计算和执行】 │
│ │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ App 安装/更新时 (Out-of-process) │ │
│ │ │ │
│ │ ┌────────────────────────────────────────────────────────┐ │ │
│ │ │ • 解析所有 Mach-O 文件 │ │ │
│ │ │ • 分析依赖关系 │ │ │
│ │ │ • 执行符号查找 │ │ │
│ │ │ • 生成 Launch Closure (缓存结果) │ │ │
│ │ └────────────────────────────────────────────────────────┘ │ │
│ │ │ │ │
│ │ ▼ │ │
│ │ ┌────────────────────────────────────────────────────────┐ │ │
│ │ │ Launch Closure (二进制缓存文件) │ │ │
│ │ │ 存储位置:/var/containers/.../Closure/ │ │ │
│ │ └────────────────────────────────────────────────────────┘ │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ App 启动时 (In-process) │ │
│ │ │ │
│ │ ┌────────────────────────────────────────────────────────┐ │ │
│ │ │ • 验证 Closure 是否有效 │ │ │
│ │ │ • 直接使用缓存的结果 │ │ │
│ │ │ • 映射动态库(利用共享缓存) │ │ │
│ │ │ • 执行 Fixups (Rebase/Bind) │ │ │
│ │ │ • 运行 Initializers │ │ │
│ │ └────────────────────────────────────────────────────────┘ │ │
│ │ │ │ │
│ │ ▼ │ │
│ │ main() │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │
│ 优势:启动时跳过大量解析和查找工作 │
│ │
└─────────────────────────────────────────────────────────────────────┘
2.3 dyld 加载详细步骤
┌─────────────────────────────────────────────────────────────────────┐
│ dyld 加载详细流程 │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ Step 1: 内核准备 (exec syscall) │ │
│ │ ┌────────────────────────────────────────────────────────┐ │ │
│ │ │ 1. fork() 创建新进程 │ │ │
│ │ │ 2. 解析 Mach-O header,验证合法性 │ │ │
│ │ │ 3. 加载 App 二进制到虚拟内存 │ │ │
│ │ │ 4. 加载 dyld 到进程地址空间 │ │ │
│ │ │ 5. 设置进程状态,跳转到 dyld 入口 (_dyld_start) │ │ │
│ │ └────────────────────────────────────────────────────────┘ │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ Step 2: dyld 初始化 │ │
│ │ ┌────────────────────────────────────────────────────────┐ │ │
│ │ │ 1. 设置运行环境(环境变量、参数) │ │ │
│ │ │ 2. 初始化 dyld 内部数据结构 │ │ │
│ │ │ 3. 检查 Launch Closure 是否有效 │ │ │
│ │ │ • 有效:使用缓存的 Closure │ │ │
│ │ │ • 无效:回退到 dyld2 模式,实时计算 │ │ │
│ │ └────────────────────────────────────────────────────────┘ │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ Step 3: 加载共享缓存 (Shared Cache) │ │
│ │ ┌────────────────────────────────────────────────────────┐ │ │
│ │ │ 共享缓存位置: │ │ │
│ │ │ /System/Library/Caches/com.apple.dyld/ │ │ │
│ │ │ │ │ │
│ │ │ 内容: │ │ │
│ │ │ • 所有系统库的合并优化版本 │ │ │
│ │ │ • 预先完成了符号绑定 │ │ │
│ │ │ • 所有进程共享,节省内存 │ │ │
│ │ │ │ │ │
│ │ │ 优势: │ │ │
│ │ │ • 系统库无需单独加载 │ │ │
│ │ │ • 符号查找极快 │ │ │
│ │ └────────────────────────────────────────────────────────┘ │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ Step 4: 实例化主程序 │ │
│ │ ┌────────────────────────────────────────────────────────┐ │ │
│ │ │ 1. 读取主程序的 Load Commands │ │ │
│ │ │ 2. 映射 __TEXT, __DATA 等 segments │ │ │
│ │ │ 3. 应用 ASLR slide │ │ │
│ │ │ 4. 创建主程序的 ImageLoader 对象 │ │ │
│ │ └────────────────────────────────────────────────────────┘ │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ Step 5: 加载动态库 │ │
│ │ ┌────────────────────────────────────────────────────────┐ │ │
│ │ │ 遍历 LC_LOAD_DYLIB 命令: │ │ │
│ │ │ │ │ │
│ │ │ for each dylib in dependencies: │ │ │
│ │ │ 1. 检查是否在共享缓存中 → 直接使用 │ │ │
│ │ │ 2. 否则从文件系统加载 │ │ │
│ │ │ 3. 递归加载该库的依赖 │ │ │
│ │ │ 4. 创建 ImageLoader 对象 │ │ │
│ │ │ │ │ │
│ │ │ 加载顺序:广度优先,按依赖层级 │ │ │
│ │ │ │ │ │
│ │ │ ⚠️ 这一步是耗时大户!动态库越多,越慢 │ │ │
│ │ └────────────────────────────────────────────────────────┘ │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ Step 6: Rebase │ │
│ │ ┌────────────────────────────────────────────────────────┐ │ │
│ │ │ ASLR 导致加载地址随机化,需要修正内部指针 │ │ │
│ │ │ │ │ │
│ │ │ 编译时地址: 0x100001234 │ │ │
│ │ │ ASLR slide: 0x000050000 │ │ │
│ │ │ 运行时地址: 0x100051234 │ │ │
│ │ │ │ │ │
│ │ │ 修正操作:遍历 __DATA 中的指针,加上 slide │ │ │
│ │ │ │ │ │
│ │ │ 影响因素: │ │ │
│ │ │ • ObjC 类越多,指针越多 │ │ │
│ │ │ • Swift 结构体比类更好(无需 rebase) │ │ │
│ │ └────────────────────────────────────────────────────────┘ │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ Step 7: Binding │ │
│ │ ┌────────────────────────────────────────────────────────┐ │ │
│ │ │ 绑定外部符号(从其他动态库导入) │ │ │
│ │ │ │ │ │
│ │ │ Non-Lazy Binding (启动时): │ │ │
│ │ │ • 全局变量引用 │ │ │
│ │ │ • __got 表中的符号 │ │ │
│ │ │ │ │ │
│ │ │ Lazy Binding (首次调用时): │ │ │
│ │ │ • 函数引用 │ │ │
│ │ │ • 通过 __stubs → __stub_helper → dyld_stub_binder │ │ │
│ │ │ │ │ │
│ │ │ Weak Binding: │ │ │
│ │ │ • 可选依赖,符号可能不存在 │ │ │
│ │ └────────────────────────────────────────────────────────┘ │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ Step 8: ObjC Runtime Setup │ │
│ │ ┌────────────────────────────────────────────────────────┐ │ │
│ │ │ 由 libobjc.dylib 中的 _objc_init 触发: │ │ │
│ │ │ │ │ │
│ │ │ 1. 注册所有 ObjC 类 │ │ │
│ │ │ • 读取 __objc_classlist │ │ │
│ │ │ • 调用 objc_readClassPair() │ │ │
│ │ │ • 建立类的继承关系 │ │ │
│ │ │ │ │ │
│ │ │ 2. 注册 Category │ │ │
│ │ │ • 读取 __objc_catlist │ │ │
│ │ │ • 将 Category 方法附加到类上 │ │ │
│ │ │ │ │ │
│ │ │ 3. 确保 Selector 唯一性 │ │ │
│ │ │ • 合并相同 selector │ │ │
│ │ │ │ │ │
│ │ │ ⚠️ 类数量直接影响这一步的耗时 │ │ │
│ │ └────────────────────────────────────────────────────────┘ │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ Step 9: Initializers │ │
│ │ ┌────────────────────────────────────────────────────────┐ │ │
│ │ │ 按依赖顺序执行初始化(被依赖的先执行): │ │ │
│ │ │ │ │ │
│ │ │ 1. libSystem.B.dylib 初始化 │ │ │
│ │ │ • dispatch_init() │ │ │
│ │ │ • libdispatch_init() │ │ │
│ │ │ • os_log_init() │ │ │
│ │ │ │ │ │
│ │ │ 2. libobjc.A.dylib 初始化 │ │ │
│ │ │ • _objc_init() │ │ │
│ │ │ • 注册 dyld 回调 │ │ │
│ │ │ │ │ │
│ │ │ 3. 其他动态库的初始化 │ │ │
│ │ │ │ │ │
│ │ │ 4. 主程序初始化 │ │ │
│ │ │ • C++ 静态构造函数 (__mod_init_func) │ │ │
│ │ │ • ObjC +load 方法 │ │ │
│ │ │ • __attribute__((constructor)) 函数 │ │ │
│ │ │ │ │ │
│ │ │ ⚠️ 这是开发者最能控制的阶段! │ │ │
│ │ └────────────────────────────────────────────────────────┘ │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ Step 10: 跳转到 main() │ │
│ │ ┌────────────────────────────────────────────────────────┐ │ │
│ │ │ 从 LC_MAIN 获取入口点偏移 │ │ │
│ │ │ 计算真实地址 = 基址 + 偏移 + ASLR slide │ │ │
│ │ │ 跳转执行 main() 函数 │ │ │
│ │ │ │ │ │
│ │ │ ✅ pre-main 阶段结束 │ │ │
│ │ └────────────────────────────────────────────────────────┘ │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────┘
三、各阶段耗时分析
3.1 获取详细统计
// 环境变量设置
/*
DYLD_PRINT_STATISTICS = 1
DYLD_PRINT_STATISTICS_DETAILS = 1
详细输出:
total time: 847.25 milliseconds (100.0%)
total images loaded: 387 (380 from dyld shared cache)
total segments mapped: 23, into 1847 pages
total images loading time: 295.52 milliseconds (34.8%)
total load time in ObjC: 89.23 milliseconds (10.5%)
total debugger pause time: 0.00 milliseconds (0.0%)
total dtrace DOF registration time: 0.00 milliseconds (0.0%)
total rebase fixups: 234,567
total rebase fixups time: 42.36 milliseconds (5.0%)
total binding fixups: 567,890
total binding fixups time: 30.44 milliseconds (3.5%)
total weak binding fixups time: 0.78 milliseconds (0.0%)
total redo coalesced images time: 5.67 milliseconds (0.6%)
total symbol trie searches: 890,123
total symbol table binary searches: 12,345
total images initializer time: 389.15 milliseconds (45.9%)
total images with lazy symbol bindings: 7
total symbol trie searches during lazy bind: 234
*/
3.2 各阶段优化重点
┌─────────────────────────────────────────────────────────────────────┐
│ 各阶段优化重点 │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ 阶段 │ 典型占比 │ 优化方向 │
│ ───────────────────┼────────────┼───────────────────────────── │
│ dylib loading │ 30-40% │ 减少动态库数量 │
│ rebase/binding │ 10-15% │ 减少 ObjC 类/指针数量 │
│ ObjC setup │ 10-15% │ 减少类/分类/协议数量 │
│ initializers │ 40-50% │ 延迟 +load,减少构造函数 │
│ │
└─────────────────────────────────────────────────────────────────────┘
四、动态库加载优化
4.1 动态库加载过程
// 查看当前加载的所有动态库
#import <mach-o/dyld.h>
void printLoadedDylibs(void) {
uint32_t count = _dyld_image_count();
NSLog(@"已加载 %u 个动态库:", count);
uint32_t fromCache = 0;
uint32_t fromFile = 0;
for (uint32_t i = 0; i < count; i++) {
const char *name = _dyld_get_image_name(i);
intptr_t slide = _dyld_get_image_vmaddr_slide(i);
// 判断是否来自共享缓存
// 共享缓存中的库 slide 通常是负值或特定模式
NSString *imageName = [NSString stringWithUTF8String:name];
if ([imageName hasPrefix:@"/System"] ||
[imageName hasPrefix:@"/usr/lib"]) {
fromCache++;
} else {
fromFile++;
NSLog(@" [文件] %s (slide: 0x%lx)", name, slide);
}
}
NSLog(@"共享缓存: %u, 文件加载: %u", fromCache, fromFile);
}
4.2 减少动态库数量
┌─────────────────────────────────────────────────────────────────────┐
│ 动态库优化策略 │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ 【策略 1: 合并动态库】 │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ Before: │ │
│ │ ├── NetworkKit.framework │ │
│ │ ├── StorageKit.framework │ │
│ │ ├── UIHelper.framework │ │
│ │ └── Utils.framework │ │
│ │ │ │
│ │ After: │ │
│ │ └── CommonKit.framework (合并上述所有) │ │
│ │ │ │
│ │ 注意:合并后单个库太大可能影响增量编译速度 │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │
│ 【策略 2: 转为静态库】 │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ 动态库 (.framework, .dylib): │ │
│ │ • 运行时加载 │ │
│ │ • 每个库都需要 mmap、rebase、bind │ │
│ │ │ │
│ │ 静态库 (.a, static .framework): │ │
│ │ • 编译时链接进主程序 │ │
│ │ • 无额外加载开销 │ │
│ │ • 但会增加主程序大小 │ │
│ │ │ │
│ │ CocoaPods: │ │
│ │ use_frameworks! :linkage => :static │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │
│ 【策略 3: 延迟加载】 │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ 使用 dlopen 按需加载: │ │
│ │ │ │
│ │ // 首屏不需要的功能库 │ │
│ │ - (void)loadShareFramework { │ │
│ │ void *handle = dlopen("ShareKit.framework/ShareKit", │ │
│ │ RTLD_LAZY); │ │
│ │ if (handle) { │ │
│ │ // 使用动态获取的符号 │ │
│ │ } │ │
│ │ } │ │
│ │ │ │
│ │ 或使用 LC_LAZY_LOAD_DYLIB: │ │
│ │ Other Linker Flags: -lazy_framework ShareKit │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │
│ 【策略 4: 检查不必要的依赖】 │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ # 查看依赖的动态库 │ │
│ │ otool -L MyApp │ │
│ │ │ │
│ │ # 检查是否有未使用的动态库 │ │
│ │ # 使用 Xcode → Build Settings → Dead Code Stripping │ │
│ │ │ │
│ │ 常见问题: │ │
│ │ • Pod 引入了过多依赖 │ │
│ │ • Debug 工具库混入 Release │ │
│ │ • 过期功能的库未移除 │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────┘
4.3 动态库加载监控
#import <mach-o/dyld.h>
#import <dlfcn.h>
// 监控动态库加载
static CFAbsoluteTime sAppStartTime;
void dylibLoadCallback(const struct mach_header *mh, intptr_t slide) {
Dl_info info;
if (dladdr(mh, &info) && info.dli_fname) {
CFAbsoluteTime now = CFAbsoluteTimeGetCurrent();
CFAbsoluteTime elapsed = (now - sAppStartTime) * 1000;
NSString *name = [[NSString stringWithUTF8String:info.dli_fname] lastPathComponent];
// 只关注非系统库
if (![name hasPrefix:@"lib"] && ![name hasPrefix:@"libsystem"]) {
NSLog(@"[%.2fms] 加载: %@", elapsed, name);
}
}
}
__attribute__((constructor))
static void setupDylibMonitor(void) {
sAppStartTime = CFAbsoluteTimeGetCurrent();
_dyld_register_func_for_add_image(dylibLoadCallback);
}
五、Rebase & Binding 优化
5.1 理解 Fixups
┌─────────────────────────────────────────────────────────────────────┐
│ Rebase & Binding 详解 │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ 【Rebase - 修正内部指针】 │
│ │
│ 代码中的内部指针示例: │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ @interface MyClass : NSObject │ │
│ │ @property (nonatomic, strong) NSString *name; │ │
│ │ @end │ │
│ │ │ │
│ │ 编译后 __DATA 中会有指针指向: │ │
│ │ • MyClass 的父类 (NSObject) │ │
│ │ • name 属性的 getter/setter 方法 │ │
│ │ • 类的方法列表 │ │
│ │ • ... │ │
│ │ │ │
│ │ 这些指针都需要加上 ASLR slide │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │
│ Rebase 信息格式(ULEB128 编码): │
│ ┌───────────────────────────────────────────────────────────────┐ │
│ │ Opcode │ 操作 │ │
│ │ ─────────────────────────────────────────────────────────── │ │
│ │ REBASE_OPCODE_SET_TYPE_IMM │ 设置指针类型 │ │
│ │ REBASE_OPCODE_SET_SEGMENT_AND_OFFSET│ 设置段和偏移 │ │
│ │ REBASE_OPCODE_ADD_ADDR_ULEB │ 移动地址 │ │
│ │ REBASE_OPCODE_DO_REBASE_IMM_TIMES │ 执行 N 次 rebase │ │
│ │ REBASE_OPCODE_DONE │ 结束 │ │
│ └───────────────────────────────────────────────────────────────┘ │
│ │
│ ════════════════════════════════════════════════════════════════ │
│ │
│ 【Binding - 绑定外部符号】 │
│ │
│ 代码中的外部引用示例: │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ #import <Foundation/Foundation.h> │ │
│ │ │ │
│ │ NSLog(@"Hello"); // 引用 Foundation 的 NSLog │ │
│ │ // 需要绑定到 Foundation.framework 中的实际地址 │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │
│ Binding 类型: │
│ ┌───────────────────────────────────────────────────────────────┐ │
│ │ 类型 │ 时机 │ 存储位置 │ │
│ │ ─────────────────────────────────────────────────────────── │ │
│ │ Non-Lazy Bind │ 启动时 │ __got │ │
│ │ Lazy Bind │ 首次调用时 │ __la_symbol_ptr │ │
│ │ Weak Bind │ 启动时 │ 可被覆盖的符号 │ │
│ └───────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────┘
5.2 查看 Fixups 数量
# 使用 dyldinfo 查看
$ dyldinfo -rebase -bind MyApp | wc -l
# 详细信息
$ dyldinfo -rebase MyApp
rebase information (from compressed dyld info):
segment section address type
__DATA __objc_classlist 0x100008000 pointer
__DATA __objc_classlist 0x100008008 pointer
...
$ dyldinfo -bind MyApp
bind information:
segment section address type addend dylib symbol
__DATA __got 0x100009000 pointer 0 libSystem _free
__DATA __got 0x100009008 pointer 0 libobjc _objc_msgSend
...
5.3 减少 Fixups 的策略
┌─────────────────────────────────────────────────────────────────────┐
│ 减少 Fixups 的策略 │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ 【1. 减少 ObjC 类数量】 │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ 每个 ObjC 类都会产生大量指针: │ │
│ │ • isa 指针 │ │
│ │ • 父类指针 │ │
│ │ • 方法列表指针 │ │
│ │ • 属性列表指针 │ │
│ │ • ivar 布局指针 │ │
│ │ • ... │ │
│ │ │ │
│ │ 优化方案: │ │
│ │ • 合并功能相近的小类 │ │
│ │ • 使用组合代替继承 │ │
│ │ • Swift 结构体代替类(值类型无需 rebase) │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │
│ 【2. 减少 C++ 虚函数】 │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ 每个有虚函数的类都有虚函数表(vtable) │ │
│ │ vtable 中的每个函数指针都需要 rebase │ │
│ │ │ │
│ │ 优化方案: │ │
│ │ • 使用 final 关键字阻止继承 │ │
│ │ • 优先使用非虚函数 │ │
│ │ • 考虑 CRTP 等编译期多态 │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │
│ 【3. 使用 Swift 优化】 │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ Swift 值类型: │ │
│ │ struct Point { │ │
│ │ var x: Double │ │
│ │ var y: Double │ │
│ │ } │ │
│ │ // 不产生 rebase/bind │ │
│ │ │ │
│ │ Swift 类(final): │ │
│ │ final class Manager { │ │
│ │ // 比 ObjC 类产生更少的指针 │ │
│ │ } │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │
│ 【4. 减少全局/静态变量】 │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ // Bad: 全局对象指针 │ │
│ │ static NSArray *kItems = @[@"a", @"b", @"c"]; │ │
│ │ │ │
│ │ // Good: 懒加载或方法内局部变量 │ │
│ │ + (NSArray *)items { │ │
│ │ static NSArray *items; │ │
│ │ static dispatch_once_t onceToken; │ │
│ │ dispatch_once(&onceToken, ^{ │ │
│ │ items = @[@"a", @"b", @"c"]; │ │
│ │ }); │ │
│ │ return items; │ │
│ │ } │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────┘
六、ObjC 运行时初始化优化
6.1 ObjC Setup 过程
// libobjc 中的初始化流程
void _objc_init(void) {
// 1. 初始化运行时环境
environ_init();
tls_init();
static_init();
runtime_init();
exception_init();
// 2. 注册 dyld 回调
_dyld_objc_notify_register(
&map_images, // 镜像加载时
&load_images, // 镜像初始化时
&unmap_image // 镜像卸载时
);
}
// map_images - 处理类注册
void map_images(unsigned count, const char * const paths[],
const struct mach_header * const mhdrs[]) {
// 遍历所有镜像
for (uint32_t i = 0; i < count; i++) {
// 读取 ObjC 元数据
_read_images(mhdrs[i], paths[i]);
}
}
// _read_images 内部会:
// 1. 读取 __objc_classlist,注册所有类
// 2. 读取 __objc_catlist,附加 Category
// 3. 读取 __objc_protolist,注册协议
// 4. 读取 __objc_selrefs,唯一化 selector
6.2 查看 ObjC 元数据量
# 查看 ObjC 相关 section 大小
$ size -m MyApp | grep objc
Section __objc_classlist: 4096
Section __objc_catlist: 512
Section __objc_protolist: 256
Section __objc_classrefs: 2048
Section __objc_selrefs: 8192
Section __objc_methname: 65536
Section __objc_methtype: 32768
# 统计类数量
$ otool -s __DATA __objc_classlist MyApp | tail -n +3 | wc -l
6.3 减少 ObjC 元数据
┌─────────────────────────────────────────────────────────────────────┐
│ 减少 ObjC 元数据 │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ 【1. 减少类数量】 │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ 分析工具: │ │
│ │ • class-dump 导出所有类 │ │
│ │ • LinkMap 分析各类大小 │ │
│ │ │ │
│ │ 常见优化: │ │
│ │ • 删除未使用的类 │ │
│ │ • 合并过度拆分的小类 │ │
│ │ • 使用 Swift struct 代替轻量级 ObjC 类 │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │
│ 【2. 减少 Category】 │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ Category 问题: │ │
│ │ • 每个 Category 都需要在启动时附加到原类 │ │
│ │ • 会增加 __objc_catlist 和方法列表 │ │
│ │ │ │
│ │ 优化方案: │ │
│ │ • 合并同一个类的多个 Category │ │
│ │ • 将工具方法改为 C 函数或 Swift 扩展 │ │
│ │ │ │
│ │ // 优化前:多个 Category │ │
│ │ @interface NSString (Format) ... @end │ │
│ │ @interface NSString (Validation) ... @end │ │
│ │ @interface NSString (Crypto) ... @end │ │
│ │ │ │
│ │ // 优化后:合并为一个 │ │
│ │ @interface NSString (Utils) │ │
│ │ // 包含 Format、Validation、Crypto 的所有方法 │ │
│ │ @end │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │
│ 【3. 优化方法签名】 │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ __objc_methname 存储方法名 │ │
│ │ __objc_methtype 存储方法类型签名 │ │
│ │ │ │
│ │ 方法名过长会增加 __objc_methname 大小: │ │
│ │ // Bad │ │
│ │ - (void)updateUserInterfaceWithAnimatedTransitionStyle... │ │
│ │ │ │
│ │ // Good │ │
│ │ - (void)updateUI:(UIStyle)style animated:(BOOL)animated; │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │
│ 【4. 使用 Swift 替代】 │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ Swift 类型的优势: │ │
│ │ │ │
│ │ • struct/enum: 无 ObjC 运行时开销 │ │
│ │ • final class: 编译器优化,减少虚函数表 │ │
│ │ • @objc 最小化: 只在必要时暴露给 ObjC │ │
│ │ │ │
│ │ // Swift 结构体,零运行时开销 │ │
│ │ struct User { │ │
│ │ var name: String │ │
│ │ var age: Int │ │
│ │ } │ │
│ │ │ │
│ │ // 需要 ObjC 互操作时才用 @objc │ │
│ │ @objc final class UserManager: NSObject { │ │
│ │ @objc func getUser() -> User { ... } │ │
│ │ } │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────┘
七、Initializers 优化
7.1 初始化器的类型
┌─────────────────────────────────────────────────────────────────────┐
│ 初始化器类型 │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ 【执行顺序】 │
│ │
│ 依赖库初始化 │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ 1. C++ 静态构造函数 (__mod_init_func) │ │
│ │ • 全局对象的构造函数 │ │
│ │ • __attribute__((constructor)) 函数 │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ 2. ObjC +load 方法 │ │
│ │ • 所有类的 +load(按编译顺序) │ │
│ │ • 所有 Category 的 +load(按编译顺序) │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ main() 函数 │
│ │
│ ════════════════════════════════════════════════════════════════ │
│ │
│ 【各类型详解】 │
│ │
│ 1. __attribute__((constructor)) │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ __attribute__((constructor)) │ │
│ │ void myInit(void) { │ │
│ │ NSLog(@"constructor called"); │ │
│ │ } │ │
│ │ │ │
│ │ // 可以指定优先级(数字越小越先执行,101起) │ │
│ │ __attribute__((constructor(101))) │ │
│ │ void earlyInit(void) { ... } │ │
│ │ │ │
│ │ __attribute__((constructor(200))) │ │
│ │ void lateInit(void) { ... } │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │
│ 2. C++ 全局对象构造 │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ // 全局对象,构造函数在 main 前执行 │ │
│ │ class Logger { │ │
│ │ public: │ │
│ │ Logger() { /* 初始化 */ } │ │
│ │ }; │ │
│ │ static Logger globalLogger; // main 前构造 │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │
│ 3. ObjC +load │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ @implementation MyClass │ │
│ │ + (void)load { │ │
│ │ // 在所有类加载后、main 前调用 │ │
│ │ // 每个类和 Category 的 +load 都会被调用 │ │
│ │ } │ │
│ │ @end │ │
│ │ │ │
│ │ 特点: │ │
│ │ • 不像普通方法那样被继承 │ │
│ │ • 自动调用,无需手动触发 │ │
│ │ • 主线程执行,阻塞启动 │ │
│ │ • 运行时环境已就绪(可以使用 ObjC 方法) │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────┘
7.2 +load 方法的问题
// 查找所有 +load 方法
#import <objc/runtime.h>
void findLoadMethods(void) {
unsigned int classCount;
Class *classes = objc_copyClassList(&classCount);
NSMutableArray *classesWithLoad = [NSMutableArray array];
for (unsigned int i = 0; i < classCount; i++) {
Class cls = classes[i];
// 检查类是否有 +load 方法(不包括继承的)
unsigned int methodCount;
Method *methods = class_copyMethodList(object_getClass(cls), &methodCount);
for (unsigned int j = 0; j < methodCount; j++) {
SEL sel = method_getName(methods[j]);
if (sel_isEqual(sel, @selector(load))) {
[classesWithLoad addObject:NSStringFromClass(cls)];
break;
}
}
free(methods);
}
free(classes);
NSLog(@"有 +load 方法的类 (%lu):\n%@",
(unsigned long)classesWithLoad.count,
[classesWithLoad componentsJoinedByString:@"\n"]);
}
7.3 +load 优化策略
┌─────────────────────────────────────────────────────────────────────┐
│ +load 优化策略 │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ 【策略 1: 迁移到 +initialize】 │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ // Before: +load │ │
│ │ + (void)load { │ │
│ │ [self setup]; │ │
│ │ } │ │
│ │ │ │
│ │ // After: +initialize (懒加载) │ │
│ │ + (void)initialize { │ │
│ │ if (self == [MyClass class]) { │ │
│ │ [self setup]; │ │
│ │ } │ │
│ │ } │ │
│ │ │ │
│ │ 区别: │ │
│ │ • +load: 类加载时立即调用 │ │
│ │ • +initialize: 第一次收到消息时才调用 │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │
│ 【策略 2: 使用 dispatch_once 延迟】 │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ // 需要某些功能时才初始化 │ │
│ │ + (instancetype)sharedInstance { │ │
│ │ static MyClass *instance; │ │
│ │ static dispatch_once_t onceToken; │ │
│ │ dispatch_once(&onceToken, ^{ │ │
│ │ instance = [[self alloc] init]; │ │
│ │ [instance setup]; // 延迟到首次使用 │ │
│ │ }); │ │
│ │ return instance; │ │
│ │ } │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │
│ 【策略 3: Method Swizzling 替代方案】 │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ // 传统:在 +load 中 swizzle │ │
│ │ + (void)load { │ │
│ │ Method original = class_getInstanceMethod(...); │ │
│ │ Method swizzled = class_getInstanceMethod(...); │ │
│ │ method_exchangeImplementations(original, swizzled); │ │
│ │ } │ │
│ │ │ │
│ │ // 替代方案 1: 在 AppDelegate 中集中处理 │ │
│ │ - (BOOL)application:didFinishLaunchingWithOptions: { │ │
│ │ [self performSwizzling]; │ │
│ │ return YES; │ │
│ │ } │ │
│ │ │ │
│ │ // 替代方案 2: 使用 Aspects 等库(首次调用时 hook) │ │
│ │ [UIViewController aspect_hookSelector:@selector(viewDidLoad)│ │
│ │ withOptions:AspectPositionBefore │ │
│ │ usingBlock:^{ ... }]; │ │
│ │ │ │
│ │ // 替代方案 3: 使用代理/组合代替继承 │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │
│ 【策略 4: 审查第三方库的 +load】 │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ 常见问题库: │ │
│ │ • 老旧的统计/监控 SDK │ │
│ │ • 热修复框架 │ │
│ │ • AOP 框架 │ │
│ │ │ │
│ │ 解决方案: │ │
│ │ • 更新到新版本 │ │
│ │ • 寻找替代库 │ │
│ │ • 联系维护者优化 │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────┘
7.4 监控 +load 耗时
// 使用 DYLD_PRINT_INITIALIZERS 环境变量
// 或者自己 hook
#import <objc/runtime.h>
#import <mach/mach_time.h>
// 注意:这需要在非常早的时机注入
// 实际项目中可以用更完善的方案
static CFMutableDictionaryRef sLoadTimes;
static mach_timebase_info_data_t sTimebaseInfo;
// Swizzle +load 的实现(概念演示,实际操作复杂)
void monitorLoadMethods(void) {
sLoadTimes = CFDictionaryCreateMutable(NULL, 0,
&kCFTypeDictionaryKeyCallBacks,
&kCFTypeDictionaryValueCallBacks);
mach_timebase_info(&sTimebaseInfo);
// 遍历所有类,记录 +load 调用时间
// 实际实现需要在 _objc_init 之前 hook
}
void printLoadTimesReport(void) {
NSLog(@"=== +load 方法耗时报告 ===");
// 按耗时排序输出
CFIndex count = CFDictionaryGetCount(sLoadTimes);
if (count == 0) return;
CFTypeRef *keys = malloc(sizeof(CFTypeRef) * count);
CFTypeRef *values = malloc(sizeof(CFTypeRef) * count);
CFDictionaryGetKeysAndValues(sLoadTimes, keys, values);
// 排序并输出...
free(keys);
free(values);
}
八、实战优化案例
8.1 优化前后对比
┌─────────────────────────────────────────────────────────────────────┐
│ 启动优化案例 │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ 【优化前】pre-main: 1247ms │
│ │
│ Total pre-main time: 1247.32 milliseconds (100.0%) │
│ dylib loading time: 456.78 milliseconds (36.6%) │
│ rebase/binding time: 123.45 milliseconds (9.8%) │
│ ObjC setup time: 167.89 milliseconds (13.4%) │
│ initializer time: 498.20 milliseconds (39.9%) │
│ slowest intializers : │
│ SDWebImage : 89.12 milliseconds (7.1%) │
│ AFNetworking : 67.34 milliseconds (5.3%) │
│ Realm : 123.45 milliseconds (9.8%) │
│ MyApp : 189.56 milliseconds (15.1%) │
│ │
│ 问题分析: │
│ 1. 动态库过多(87个自定义 framework) │
│ 2. ObjC 类过多(2341个) │
│ 3. +load 方法过多(156个) │
│ 4. 第三方库初始化耗时 │
│ │
│ ════════════════════════════════════════════════════════════════ │
│ │
│ 【优化措施】 │
│ │
│ 1. 动态库优化: │
│ • 合并业务 Framework: 87 → 12 │
│ • 小型工具库改为静态链接 │
│ • 非首屏功能库延迟加载 │
│ │
│ 2. 类数量优化: │
│ • 删除无用类: -234 │
│ • 合并小类: -156 │
│ • Swift struct 替代: -89 │
│ • 最终: 2341 → 1862 │
│ │
│ 3. +load 优化: │
│ • 迁移到 +initialize: 89个 │
│ • 延迟到 didFinishLaunching: 45个 │
│ • 删除无用的: 12个 │
│ • 最终: 156 → 10 │
│ │
│ 4. 第三方库优化: │
│ • SDWebImage: 延迟初始化图片缓存 │
│ • Realm: 延迟到首次数据库操作 │
│ • 更新 AFNetworking 到最新版 │
│ │
│ ════════════════════════════════════════════════════════════════ │
│ │
│ 【优化后】pre-main: 523ms (减少 58%) │
│ │
│ Total pre-main time: 523.67 milliseconds (100.0%) │
│ dylib loading time: 178.34 milliseconds (34.0%) │
│ rebase/binding time: 78.12 milliseconds (14.9%) │
│ ObjC setup time: 98.45 milliseconds (18.7%) │
│ initializer time: 168.76 milliseconds (32.2%) │
│ slowest intializers : │
│ libSystem.B.dylib : 8.90 milliseconds │
│ MyApp : 134.56 milliseconds │
│ │
└─────────────────────────────────────────────────────────────────────┘
8.2 完整的启动优化检查清单
// StartupOptimizer.h
@interface StartupOptimizer : NSObject
// 分析当前状态
+ (void)analyzeStartupPerformance;
// 各项检查
+ (NSArray<NSString *> *)checkDylibCount;
+ (NSArray<NSString *> *)checkObjCClassCount;
+ (NSArray<NSString *> *)checkLoadMethods;
+ (NSArray<NSString *> *)checkLargeInitializers;
@end
#import "StartupOptimizer.h"
#import <mach-o/dyld.h>
#import <objc/runtime.h>
#import <dlfcn.h>
@implementation StartupOptimizer
+ (void)analyzeStartupPerformance {
NSLog(@"========== 启动性能分析 ==========");
// 1. 动态库数量
NSArray *dylibIssues = [self checkDylibCount];
NSLog(@"\n【动态库分析】");
for (NSString *issue in dylibIssues) {
NSLog(@" %@", issue);
}
// 2. ObjC 类数量
NSArray *classIssues = [self checkObjCClassCount];
NSLog(@"\n【ObjC 类分析】");
for (NSString *issue in classIssues) {
NSLog(@" %@", issue);
}
// 3. +load 方法
NSArray *loadIssues = [self checkLoadMethods];
NSLog(@"\n【+load 方法分析】");
for (NSString *issue in loadIssues) {
NSLog(@" %@", issue);
}
NSLog(@"\n===================================");
}
#pragma mark - 动态库检查
+ (NSArray<NSString *> *)checkDylibCount {
NSMutableArray *results = [NSMutableArray array];
uint32_t count = _dyld_image_count();
uint32_t systemCount = 0;
uint32_t customCount = 0;
NSMutableArray *customDylibs = [NSMutableArray array];
for (uint32_t i = 0; i < count; i++) {
const char *name = _dyld_get_image_name(i);
NSString *imageName = [NSString stringWithUTF8String:name];
// 判断是否是系统库
if ([imageName hasPrefix:@"/System"] ||
[imageName hasPrefix:@"/usr/lib"] ||
[imageName hasPrefix:@"/Library/Apple"]) {
systemCount++;
} else {
customCount++;
NSString *shortName = [imageName lastPathComponent];
[customDylibs addObject:shortName];
}
}
[results addObject:[NSString stringWithFormat:@"总计: %u 个动态库", count]];
[results addObject:[NSString stringWithFormat:@"系统库: %u 个 (共享缓存)", systemCount]];
[results addObject:[NSString stringWithFormat:@"自定义库: %u 个", customCount]];
// 警告阈值
if (customCount > 6) {
[results addObject:[NSString stringWithFormat:
@"⚠️ 警告: 自定义动态库超过 6 个,建议合并或转为静态库"]];
}
// 列出所有自定义库
if (customDylibs.count > 0) {
[results addObject:@"自定义动态库列表:"];
for (NSString *name in customDylibs) {
[results addObject:[NSString stringWithFormat:@" • %@", name]];
}
}
return results;
}
#pragma mark - ObjC 类检查
+ (NSArray<NSString *> *)checkObjCClassCount {
NSMutableArray *results = [NSMutableArray array];
unsigned int classCount = 0;
Class *classes = objc_copyClassList(&classCount);
// 按来源分类
NSMutableDictionary<NSString *, NSMutableArray *> *classesByImage = [NSMutableDictionary dictionary];
for (unsigned int i = 0; i < classCount; i++) {
Class cls = classes[i];
Dl_info info;
if (dladdr((__bridge void *)cls, &info) && info.dli_fname) {
NSString *imagePath = [NSString stringWithUTF8String:info.dli_fname];
NSString *imageName = [imagePath lastPathComponent];
if (!classesByImage[imageName]) {
classesByImage[imageName] = [NSMutableArray array];
}
[classesByImage[imageName] addObject:NSStringFromClass(cls)];
}
}
free(classes);
[results addObject:[NSString stringWithFormat:@"总类数: %u", classCount]];
// 按库统计类数量,找出类最多的库
NSArray *sortedImages = [classesByImage keysSortedByValueUsingComparator:
^NSComparisonResult(NSMutableArray *a1, NSMutableArray *a2) {
return [@(a2.count) compare:@(a1.count)];
}];
[results addObject:@"各模块类数量 (Top 10):"];
NSUInteger showCount = MIN(10, sortedImages.count);
for (NSUInteger i = 0; i < showCount; i++) {
NSString *image = sortedImages[i];
NSArray *imageClasses = classesByImage[image];
[results addObject:[NSString stringWithFormat:@" %@: %lu 个类",
image, (unsigned long)imageClasses.count]];
}
// 警告
if (classCount > 2000) {
[results addObject:@"⚠️ 警告: ObjC 类数量过多,影响 ObjC Setup 耗时"];
[results addObject:@" 建议: 删除无用类、使用 Swift struct、合并小类"];
}
return results;
}
#pragma mark - +load 方法检查
+ (NSArray<NSString *> *)checkLoadMethods {
NSMutableArray *results = [NSMutableArray array];
NSMutableArray<NSString *> *classesWithLoad = [NSMutableArray array];
NSMutableArray<NSString *> *categoriesWithLoad = [NSMutableArray array];
unsigned int classCount = 0;
Class *classes = objc_copyClassList(&classCount);
for (unsigned int i = 0; i < classCount; i++) {
Class cls = classes[i];
Class metaClass = object_getClass(cls);
// 检查类本身的 +load(不包括继承的)
unsigned int methodCount = 0;
Method *methods = class_copyMethodList(metaClass, &methodCount);
BOOL hasLoad = NO;
for (unsigned int j = 0; j < methodCount; j++) {
SEL sel = method_getName(methods[j]);
if (sel_isEqual(sel, @selector(load))) {
hasLoad = YES;
break;
}
}
if (methods) free(methods);
if (hasLoad) {
NSString *className = NSStringFromClass(cls);
// 简单判断是否是 Category 的 +load
// (实际上需要更复杂的检测)
if ([className containsString:@"("]) {
[categoriesWithLoad addObject:className];
} else {
[classesWithLoad addObject:className];
}
}
}
free(classes);
NSUInteger totalLoad = classesWithLoad.count + categoriesWithLoad.count;
[results addObject:[NSString stringWithFormat:@"+load 方法总数: %lu",
(unsigned long)totalLoad]];
[results addObject:[NSString stringWithFormat:@" 类的 +load: %lu",
(unsigned long)classesWithLoad.count]];
[results addObject:[NSString stringWithFormat:@" Category 的 +load: %lu",
(unsigned long)categoriesWithLoad.count]];
// 列出所有 +load
if (classesWithLoad.count > 0) {
[results addObject:@"有 +load 的类:"];
for (NSString *cls in classesWithLoad) {
[results addObject:[NSString stringWithFormat:@" • %@", cls]];
}
}
// 警告
if (totalLoad > 10) {
[results addObject:@"⚠️ 警告: +load 方法过多,严重影响启动速度"];
[results addObject:@" 建议: 迁移到 +initialize 或 dispatch_once"];
}
return results;
}
#pragma mark - 大型初始化器检查
+ (NSArray<NSString *> *)checkLargeInitializers {
NSMutableArray *results = [NSMutableArray array];
// 这部分需要配合 DYLD_PRINT_STATISTICS_DETAILS 环境变量
// 或使用 Instruments 分析
[results addObject:@"请使用以下方法分析初始化耗时:"];
[results addObject:@"1. 设置环境变量 DYLD_PRINT_STATISTICS_DETAILS=1"];
[results addObject:@"2. 使用 Instruments - App Launch"];
[results addObject:@"3. 使用 os_signpost 自定义打点"];
return results;
}
@end
8.3 启动耗时打点系统
// LaunchProfiler.h
#import <Foundation/Foundation.h>
@interface LaunchProfiler : NSObject
+ (instancetype)shared;
// 标记阶段开始
- (void)markStart:(NSString *)stage;
// 标记阶段结束
- (void)markEnd:(NSString *)stage;
// 标记时间点
- (void)markMilestone:(NSString *)name;
// 生成报告
- (void)printReport;
- (NSDictionary *)getReport;
@end
// LaunchProfiler.m
#import "LaunchProfiler.h"
#import <mach/mach_time.h>
#import <os/signpost.h>
@interface LaunchStage : NSObject
@property (nonatomic, assign) uint64_t startTime;
@property (nonatomic, assign) uint64_t endTime;
@property (nonatomic, copy) NSString *name;
@end
@implementation LaunchStage
- (double)durationMs {
static mach_timebase_info_data_t timebase;
if (timebase.denom == 0) {
mach_timebase_info(&timebase);
}
uint64_t elapsed = self.endTime - self.startTime;
return (double)elapsed * timebase.numer / timebase.denom / 1000000.0;
}
@end
@interface LaunchProfiler ()
@property (nonatomic, strong) NSMutableDictionary<NSString *, LaunchStage *> *stages;
@property (nonatomic, strong) NSMutableArray<NSDictionary *> *milestones;
@property (nonatomic, assign) uint64_t processStartTime;
@property (nonatomic, strong) os_log_t log;
@end
@implementation LaunchProfiler
+ (instancetype)shared {
static LaunchProfiler *instance;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
instance = [[LaunchProfiler alloc] init];
});
return instance;
}
- (instancetype)init {
self = [super init];
if (self) {
_stages = [NSMutableDictionary dictionary];
_milestones = [NSMutableArray array];
_processStartTime = [self getProcessStartTime];
_log = os_log_create("com.app.launch", "profiler");
}
return self;
}
- (uint64_t)getProcessStartTime {
// 获取进程启动时间
struct kinfo_proc info;
size_t size = sizeof(info);
int mib[4] = { CTL_KERN, KERN_PROC, KERN_PROC_PID, getpid() };
if (sysctl(mib, 4, &info, &size, NULL, 0) == 0) {
struct timeval startTime = info.kp_proc.p_starttime;
return startTime.tv_sec * 1000000000ULL + startTime.tv_usec * 1000ULL;
}
return mach_absolute_time();
}
- (void)markStart:(NSString *)stage {
uint64_t now = mach_absolute_time();
LaunchStage *stageObj = [[LaunchStage alloc] init];
stageObj.name = stage;
stageObj.startTime = now;
self.stages[stage] = stageObj;
// os_signpost 集成 (Instruments 可视化)
if (@available(iOS 12.0, *)) {
os_signpost_interval_begin(self.log, OS_SIGNPOST_ID_EXCLUSIVE,
"Launch", "%{public}s", stage.UTF8String);
}
}
- (void)markEnd:(NSString *)stage {
uint64_t now = mach_absolute_time();
LaunchStage *stageObj = self.stages[stage];
if (stageObj) {
stageObj.endTime = now;
}
if (@available(iOS 12.0, *)) {
os_signpost_interval_end(self.log, OS_SIGNPOST_ID_EXCLUSIVE,
"Launch", "%{public}s", stage.UTF8String);
}
}
- (void)markMilestone:(NSString *)name {
uint64_t now = mach_absolute_time();
static mach_timebase_info_data_t timebase;
if (timebase.denom == 0) {
mach_timebase_info(&timebase);
}
uint64_t elapsed = now - self.processStartTime;
double ms = (double)elapsed * timebase.numer / timebase.denom / 1000000.0;
[self.milestones addObject:@{
@"name": name,
@"time": @(ms)
}];
if (@available(iOS 12.0, *)) {
os_signpost_event_emit(self.log, OS_SIGNPOST_ID_EXCLUSIVE,
"Milestone", "%{public}s at %.2fms",
name.UTF8String, ms);
}
}
- (void)printReport {
NSLog(@"\n========== 启动耗时报告 ==========");
// 按开始时间排序阶段
NSArray *sortedStages = [self.stages.allValues sortedArrayUsingComparator:
^NSComparisonResult(LaunchStage *s1, LaunchStage *s2) {
return s1.startTime < s2.startTime ? NSOrderedAscending : NSOrderedDescending;
}];
double totalTime = 0;
for (LaunchStage *stage in sortedStages) {
if (stage.endTime > 0) {
double duration = [stage durationMs];
totalTime += duration;
NSLog(@" %@: %.2f ms", stage.name, duration);
}
}
NSLog(@"\n里程碑时间点:");
for (NSDictionary *milestone in self.milestones) {
NSLog(@" [%.2f ms] %@",
[milestone[@"time"] doubleValue],
milestone[@"name"]);
}
NSLog(@"\n各阶段总耗时: %.2f ms", totalTime);
NSLog(@"===================================\n");
}
- (NSDictionary *)getReport {
NSMutableDictionary *report = [NSMutableDictionary dictionary];
NSMutableArray *stageReports = [NSMutableArray array];
for (LaunchStage *stage in self.stages.allValues) {
if (stage.endTime > 0) {
[stageReports addObject:@{
@"name": stage.name,
@"duration": @([stage durationMs])
}];
}
}
report[@"stages"] = stageReports;
report[@"milestones"] = self.milestones;
return report;
}
@end
8.4 在 App 中使用
// main.m
#import "LaunchProfiler.h"
int main(int argc, char * argv[]) {
[[LaunchProfiler shared] markMilestone:@"main() 开始"];
NSString * appDelegateClassName;
@autoreleasepool {
[[LaunchProfiler shared] markMilestone:@"autoreleasepool 进入"];
appDelegateClassName = NSStringFromClass([AppDelegate class]);
}
[[LaunchProfiler shared] markStart:@"UIApplicationMain"];
return UIApplicationMain(argc, argv, nil, appDelegateClassName);
}
// AppDelegate.m
- (BOOL)application:(UIApplication *)application
didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
[[LaunchProfiler shared] markEnd:@"UIApplicationMain"];
[[LaunchProfiler shared] markMilestone:@"didFinishLaunching 开始"];
// 配置工作
[[LaunchProfiler shared] markStart:@"初始化配置"];
[self setupConfiguration];
[[LaunchProfiler shared] markEnd:@"初始化配置"];
// SDK 初始化
[[LaunchProfiler shared] markStart:@"SDK 初始化"];
[self initializeSDKs];
[[LaunchProfiler shared] markEnd:@"SDK 初始化"];
// UI 配置
[[LaunchProfiler shared] markStart:@"UI 配置"];
[self setupUI];
[[LaunchProfiler shared] markEnd:@"UI 配置"];
[[LaunchProfiler shared] markMilestone:@"didFinishLaunching 结束"];
// 延迟打印报告
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 2 * NSEC_PER_SEC),
dispatch_get_main_queue(), ^{
[[LaunchProfiler shared] printReport];
});
return YES;
}
// ViewController.m
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
[[LaunchProfiler shared] markMilestone:@"首屏渲染完成"];
[[LaunchProfiler shared] printReport];
});
}
九、高级优化技巧
9.1 二进制重排
┌─────────────────────────────────────────────────────────────────────┐
│ 二进制重排优化 │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ 【问题】 │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ 代码在二进制中的排列顺序是编译顺序(文件顺序) │ │
│ │ 启动时需要的代码可能分散在多个内存页中 │ │
│ │ │ │
│ │ Page 1 Page 2 Page 3 Page 4 Page 5 │ │
│ │ ┌─────┐ ┌─────┐ ┌─────┐ ┌─────┐ ┌─────┐ │ │
│ │ │A.m │ │B.m │ │C.m │ │D.m │ │E.m │ │ │
│ │ │启动✓│ │ │ │启动✓│ │ │ │启动✓│ │ │
│ │ └─────┘ └─────┘ └─────┘ └─────┘ └─────┘ │ │
│ │ │ │
│ │ 启动需要加载 3 个页面,产生 3 次 Page Fault │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │
│ 【优化后】 │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ 将启动需要的函数放在一起 │ │
│ │ │ │
│ │ Page 1 Page 2 Page 3 Page 4 Page 5 │ │
│ │ ┌───────────┐ ┌─────┐ ┌─────┐ ┌─────┐ ┌─────┐ │ │
│ │ │A.m 启动✓ │ │B.m │ │ │ │D.m │ │ │ │ │
│ │ │C.m 启动✓ │ │ │ │ │ │ │ │ │ │ │
│ │ │E.m 启动✓ │ │ │ │ │ │ │ │ │ │ │
│ │ └───────────┘ └─────┘ └─────┘ └─────┘ └─────┘ │ │
│ │ │ │
│ │ 启动只需加载 1 个页面,1 次 Page Fault │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────┘
9.2 获取启动时调用的函数
// 方法 1: 使用 Clang SanitizerCoverage
// Build Settings:
// Other C Flags: -fsanitize-coverage=func,trace-pc-guard
// Other Swift Flags: -sanitize-coverage=func -sanitize=undefined
// 回调函数
void __sanitizer_cov_trace_pc_guard_init(uint32_t *start, uint32_t *stop) {
static uint64_t N;
if (start == stop || *start) return;
for (uint32_t *x = start; x < stop; x++) {
*x = ++N;
}
}
static NSMutableArray<NSString *> *sCoveredFunctions;
static OSSpinLock sLock = OS_SPINLOCK_INIT;
void __sanitizer_cov_trace_pc_guard(uint32_t *guard) {
if (!*guard) return;
void *PC = __builtin_return_address(0);
Dl_info info;
dladdr(PC, &info);
OSSpinLockLock(&sLock);
if (!sCoveredFunctions) {
sCoveredFunctions = [NSMutableArray array];
}
NSString *funcName = @(info.dli_sname ?: "unknown");
if (![sCoveredFunctions containsObject:funcName]) {
[sCoveredFunctions addObject:funcName];
}
OSSpinLockUnlock(&sLock);
*guard = 0;
}
// 导出启动时调用的函数列表
void exportLaunchFunctions(void) {
NSString *path = [NSTemporaryDirectory() stringByAppendingPathComponent:@"launch_functions.txt"];
NSString *content = [sCoveredFunctions componentsJoinedByString:@"\n"];
[content writeToFile:path atomically:YES encoding:NSUTF8StringEncoding error:nil];
NSLog(@"导出到: %@", path);
}
9.3 生成 Order File
# 1. 收集启动时调用的符号(从上面的方法获取)
# 2. 创建 order 文件
# launch.order 内容示例:
_main
_applicationDidFinishLaunching
-[AppDelegate application:didFinishLaunchingWithOptions:]
-[RootViewController viewDidLoad]
-[NetworkManager shared]
...
# 3. 配置 Xcode
# Build Settings → Linking → Order File
# $(PROJECT_DIR)/launch.order
9.4 App 预热 (App Prewarming)
┌─────────────────────────────────────────────────────────────────────┐
│ iOS 15+ App 预热 │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ iOS 15 引入了 App 预热机制: │
│ │
│ • 系统会预测用户可能打开的 App │
│ • 提前在后台执行 dyld 加载过程 │
│ • 当用户真正打开时,直接从预热状态恢复 │
│ │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ 预热流程: │ │
│ │ │ │
│ │ 系统预测 ─→ dyld 加载 ─→ 运行到 main() 前 ─→ 暂停 │ │
│ │ │ │ │
│ │ ▼ │ │
│ │ 用户点击 App 图标 │ │
│ │ │ │ │
│ │ ▼ │ │
│ │ 从暂停点继续执行 │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │
│ 检测预热启动: │
│ │
│ if (@available(iOS 15.0, *)) { │
│ NSDictionary *env = [[NSProcessInfo processInfo] environment];│
│ BOOL isPrewarmed = [env[@"ActivePrewarm"] boolValue]; │
│ if (isPrewarmed) { │
│ NSLog(@"这是预热启动"); │
│ } │
│ } │
│ │
│ 注意事项: │
│ • 预热时不应该执行网络请求 │
│ • 预热时不应该显示 UI │
│ • 预热时不应该做用户相关的初始化 │
│ │
└─────────────────────────────────────────────────────────────────────┘
// 处理预热启动
@implementation AppDelegate
- (BOOL)application:(UIApplication *)application
didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
BOOL isPrewarmed = NO;
if (@available(iOS 15.0, *)) {
isPrewarmed = [[[NSProcessInfo processInfo] environment][@"ActivePrewarm"] boolValue];
}
if (isPrewarmed) {
// 预热启动,延迟某些初始化
NSLog(@"预热启动,延迟初始化");
[self deferredInitialization];
} else {
// 正常启动
[self normalInitialization];
}
return YES;
}
- (void)deferredInitialization {
// 只做最基础的初始化
// 其他工作等用户真正使用时再做
dispatch_async(dispatch_get_main_queue(), ^{
[self normalInitialization];
});
}
- (void)normalInitialization {
// 完整的初始化流程
[self initializeAnalytics];
[self initializeNetworking];
[self setupUI];
}
@end
十、总结与最佳实践
10.1 优化检查清单
┌─────────────────────────────────────────────────────────────────────┐
│ 启动优化检查清单 │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ 【动态库优化】 │
│ □ 自定义动态库数量 ≤ 6 │
│ □ 小型工具库转为静态链接 │
│ □ 非必要库延迟加载 │
│ □ 合并功能相近的 Framework │
│ │
│ 【Rebase/Binding 优化】 │
│ □ ObjC 类数量合理(建议 < 2000) │
│ □ 减少 C++ 虚函数 │
│ □ 优先使用 Swift 值类型 │
│ □ 减少全局变量和静态变量 │
│ │
│ 【ObjC Setup 优化】 │
│ □ 删除无用类 │
│ □ 合并 Category │
│ □ 减少协议数量 │
│ □ Swift 新代码优先用 struct │
│ │
│ 【Initializer 优化】 │
│ □ +load 方法数量 ≤ 10 │
│ □ +load 迁移到 +initialize │
│ □ 避免 __attribute__((constructor)) │
│ □ C++ 全局对象使用懒加载 │
│ │
│ 【main() 后优化】 │
│ □ didFinishLaunching 中只做必要工作 │
│ □ 非首屏 SDK 延迟初始化 │
│ □ 首屏 UI 尽量简单 │
│ □ 避免同步网络请求 │
│ □ 避免大量 IO 操作 │
│ □ 图片使用合适大小和格式 │
│ │
│ 【高级优化】 │
│ □ 二进制重排(Order File) │
│ □ 处理 App 预热(iOS 15+) │
│ □ 使用 MetricKit 监控线上数据 │
│ │
│ 【目标】 │
│ □ 冷启动 pre-main < 400ms │
│ □ 冷启动总时间 < 1s │
│ □ 热启动 < 200ms │
│ │
└─────────────────────────────────────────────────────────────────────┘
10.2 常用工具汇总
| 工具 | 用途 | 使用方式 |
|---|---|---|
DYLD_PRINT_STATISTICS | 查看 pre-main 各阶段耗时 | 环境变量 |
DYLD_PRINT_LIBRARIES | 查看加载的动态库 | 环境变量 |
| Instruments - App Launch | 完整启动分析 | Xcode |
| Instruments - Time Profiler | CPU 热点分析 | Xcode |
| Instruments - System Trace | 系统调用分析 | Xcode |
otool -L | 查看动态库依赖 | 命令行 |
nm | 查看符号 | 命令行 |
size | 查看 section 大小 | 命令行 |
| LinkMap | 分析包体积组成 | Xcode 输出 |
| MetricKit | 线上性能监控 | iOS API |
10.3 持续优化建议
┌─────────────────────────────────────────────────────────────────────┐
│ 持续优化流程 │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────┐ │
│ │ 建立基线 │ │
│ │ 测量数据 │ │
│ └──────┬──────┘ │
│ │ │
│ ▼ │
│ ┌─────────────┐ │
│ │ 分析问题 │ │
│ │ 定位瓶颈 │ │
│ └──────┬──────┘ │
│ │ │
│ ▼ │
│ ┌─────────────┐ │
│ │ 制定方案 │ │
│ │ 优先排序 │ │
│ └──────┬──────┘ │
│ │ │
│ ▼ │
│ ┌─────────────┐ │
│ │ 实施优化 │ │
│ │ A/B 测试 │ │
│ └──────┬──────┘ │
│ │ │
│ ▼ │
│ ┌─────────────┐ │
│ │ 验证效果 │ │
│ │ 收集反馈 │────────────────────────────┐ │
│ └──────┬──────┘ │ │
│ │ │ │
│ ▼ │ │
│ ┌─────────────┐ │ │
│ │ 线上监控 │ │ │
│ │ 持续跟踪 │────────────────────────────┘ │
│ └─────────────┘ │
│ │
│ 关键指标: │
│ • P50/P90/P99 启动时间 │
│ • 启动超时率(>2s 占比) │
│ • 不同机型/系统的启动时间分布 │
│ │
└─────────────────────────────────────────────────────────────────────┘
3万+

被折叠的 条评论
为什么被折叠?



