Appearance
01 - iOS 调试深度全栈
目录
- Xcode 调试概览
- 断点调试系统
- LLDB 命令详解
- LLDB 执行控制
- LLDB 内存与变量操作
- LLDB 高级特性
- LLDB 脚本与自定义命令
- Instruments 工具链
- Time Profiler 深度分析
- Allocations 内存分配追踪
- Leaks 内存泄漏检测
- Zombies 悬垂指针检测
- Core Animation 渲染分析
- Network & Energy 工具
- System Trace 系统级追踪
- 模拟器调试
- 崩溃日志分析
- Sanitizer 运行时检测
- 内存调试实战
- 调试与 Android/Kotlin 对比
- 面试考点汇总
1. Xcode 调试概览
1.1 调试工具链全景
iOS 调试工具链全景架构:
┌──────────────────────────────────────────────────────────────────────┐
│ Xcode Debugging Ecosystem │
├──────────────┬──────────────────────┬───────────────────────────────┤
│ │ 编译期检测 │ 运行时检测 │
│ ├──────────────────────┼───────────────────────────────┤
│ 静态分析 │ ⚠️ 编译器警告 │ Sanitizers (ASan/TSan/UBSan) │
│ (Analyzer) │ • 空值检查 │ • 地址安全检测 │
│ │ • 未使用变量 │ • 线程安全检测 │
│ │ • 内存管理 │ • 未定义行为检测 │
│ │ • 死代码检测 │ │
│ ├──────────────────────┼───────────────────────────────┤
│ 断点调试 │ LLDB 调试器 │ 断点系统 │
│ (LLDB) │ • 变量检查 │ • 条件断点 │
│ │ • 表达式求值 │ • 日志断点 │
│ │ • 内存读写 │ • 异常断点 │
│ │ • 堆栈回溯 │ • 观察断点(Watchpoint) │
│ ├──────────────────────┼───────────────────────────────┤
│ 性能分析 │ Instruments 工具集 │ 模拟器/真机调试 │
│ (Profiling) │ • Time Profiler │ • Simulator │
│ │ • Allocations │ • Console (设备日志) │
│ │ • Leaks │ • Crash Reports │
│ │ • Zombies │ • GPU Frame Capture │
│ │ • Core Animation │ • Memory Pressure │
│ │ • System Trace │ │
│ │ • Network/Energy │ │
└──────────────┴──────────────────────┴───────────────────────────────┘1.2 Xcode 调试入口
Xcode 调试入口方式:
┌──────────────────────────────────────────────────────────────────────┐
│ 方式 │ 快捷键 │ 说明 │
├──────────────────────────────────────────────────────────────────────┤
│ 启动调试运行 │ ⌘ + R │ 启动并附加 LLDB │
│ Profile (Instruments) │ ⌘ + I │ 打开 Instruments 分析工具 │
│ 断点导航 │ ⌘ + Shift + L │ 导航列表,搜索断点/符号 │
│ 控制台 │ ⌘ + Shift + C │ 打开/隐藏 LLDB 控制台 │
│ 调试栏 │ (显示/隐藏) │ 显示 step/continue/stop 控制 │
│ 变量观察器 │ (自动) │ 断点命中时自动显示变量 │
│ 内存图 │ (Instruments) │ 可视化内存布局 │
└──────────────────────────────────────────────────────────────────────┘
关键路径:
Xcode → Product → Profile (⌘ + I) → Instruments
Xcode → Window → Debug Navigator (⌘ + 5) → 调试面板
Xcode → Window → Debugging → Variables (⌘ + 6) → 变量观察器
Xcode → Window → Debugging → Memory (⌘ + 7) → 内存视图
Xcode → Window → Debugging → Threads (⌘ + 8) → 线程面板
Xcode → Window → Debugging → Console (⌘ + 9) → LLDB 控制台
Xcode → Window → Devices and Simulators (⌘ + Shift + 2) → 设备管理1.3 断点类型详解
断点类型完整分类:
┌──────────────────────────────────────────────────────────────────────┐
│ 类型 │ 触发条件 │ 调试场景 │ 实现原理 │
├──────────────────────────────────────────────────────────────────────┤
│ 基础断点 │ 代码行号 │ 逐行调试 │ SIGTRAP 信号 │
│ 条件断点 │ 表达式为真 │ 特定数据状态 │ 条件检查+SIGTRAP │
│ 符号断点 │ 函数/方法名 │ 拦截外部调用 │ dyld 符号解析 │
│ 异常断点 │ 抛出 Objective-C/C++ │ 捕获所有异常 │ NSException 监听 │
│ │ 异常 │ │ │
│ 日志断点 │ 命中时(不停止) │ 跟踪调用流 │ NSLog + continue │
│ │ │ │ │
│ 观察断点 │ 变量读写 │ 追踪内存变更 │ 硬件断点寄存器 │
│ (Watchpoint) │ │ │ HW debug registers │
│ 动态断点 │ 函数进入/返回 │ 动态插入调试逻辑 │ JIT 代码注入 │
│ 崩溃断点 │ 程序崩溃 │ 定位崩溃现场 │ signal handler │
│ Mach 异常 │ │ │ │
│ 线程断点 │ 线程创建/销毁 │ 监控线程生命周期 │ pthread hooks │
│ 库加载断点 │ 动态库加载/卸载 │ 分析库初始化 │ dyld hooks │
│ 路径断点 │ 文件路径访问 │ 追踪文件操作 │ ktrace + VFS hook │
└──────────────────────────────────────────────────────────────────────┘1.4 调试流程全景
iOS 完整调试流程:
开发阶段 测试阶段 生产阶段
│ │ │
▼ ▼ ▼
┌───────────┐ ┌───────────────┐ ┌───────────────┐
│ 代码编写 │ │ Instruments │ │ 崩溃报告 │
│ LLDB 调试 │ ──────→ │ Profiling │ ──────→ │ Crash Reports │
│ 断点/变量 │ │ Sanitizers │ │ dSYM 符号化 │
│ 内存检查 │ │ Simulator │ │ TestFlight │
└───────────┘ └───────────────┘ └───────────────┘
│ │ │
▼ ▼ ▼
修复代码 性能优化 远程分析
验证修复 内存优化 后续版本修复2. 断点调试系统
2.1 断点底层原理
断点(Breakpoint)底层实现原理:
┌──────────────────────────────────────────────────────────────────────┐
│ 断点设置流程(LLDB + 内核协作): │
│ │
│ 1. 用户设置断点 (breakpoint set --name "func") │
│ 2. LLDB 查找符号地址 (dlopen + dladdr / dyld lookup) │
│ 3. LLDB 修改目标进程内存: │
│ • x86/x86_64:将第一条指令替换为 0xCC (INT3) │
│ • ARM64:将第一条指令替换为 0xD4200000 (BRK #0x1) │
│ 4. 目标进程执行到断点 → CPU 触发异常 │
│ 5. 内核通过 ptrace 通知 LLDB(SIGTRAP) │
│ 6. LLDB 接管控制权,暂停进程 │
│ 7. 用户执行 LLDB 命令 │
│ 8. 用户点击 continue → LLDB 恢复原始指令,进程继续执行 │
│ │
│ x86_64 断点指令替换: │
│ ┌───────┬──────────┬──────────────────────────┬──────────────────┐ │
│ │ 状态 │ 机器码 │ 指令 │ 字节数 │ │
│ ├───────┼──────────┼──────────────────────────┼──────────────────┤ │
│ │ 原始 │ 55 48 89 │ push rbp; mov rbp,rsp │ N 字节 │ │
│ │ 断点后 │ CC │ int 3 (SIGTRAP) │ 1 字节 (可伸缩) │ │
│ └───────┴──────────┴──────────────────────────┴──────────────────┘ │
│ │
│ ARM64 断点指令替换: │
│ ┌───────┬──────────────┬──────────────────────────┬───────────────┐ │
│ │ 状态 │ 机器码 │ 指令 │ 字节数 │ │
│ ├───────┼──────────────┼──────────────────────────┼───────────────┤ │
│ │ 原始 │ D10043FF │ sub sp, sp, #0x40 │ 4 字节 │ │
│ │ 断点后 │ D4200000 │ brk #0x1 │ 4 字节 │ │
│ └───────┴──────────────┴──────────────────────────┴───────────────┘ │
└──────────────────────────────────────────────────────────────────────┘2.2 条件断点深度分析
条件断点(Conditional Breakpoint)原理:
┌──────────────────────────────────────────────────────────────────────┐
│ 条件断点工作流程: │
│ │
│ LLDB 在条件断点处插入"检查器代码": │
│ │
│ ┌──────────────────────────────────────────────────────────────┐ │
│ │ 原始断点: BRK #0x1 │ │
│ │ ↓ │ │
│ │ LLDB 插入检查代码: │ │
│ │ ┌──────────────────────────────────────────────────────┐ │ │
│ │ │ 1. 触发断点 (BRK #0x1) │ │ │
│ │ │ 2. LLDB 捕获 SIGTRAP │ │ │
│ │ │ 3. 评估条件表达式 (如 i == 100) │ │ │
│ │ │ 4a. 条件为真 → 真正暂停,显示变量 │ │ │
│ │ │ 4b. 条件为假 → LLDB 恢复指令,进程继续执行 │ │ │
│ │ └──────────────────────────────────────────────────────┘ │ │
│ └──────────────────────────────────────────────────────────────┘ │
│ │
│ 条件表达式支持的语言: │
│ • C 表达式(默认) │
│ • Swift 表达式(Xcode 11+) │
│ • OC 表达式 │
│ │
│ 常见条件表达式: │
│ • 数值条件: i == 100 && j < 50 │
│ • 字符串条件: [name isEqualToString:@"test"] │
│ • 对象条件: self.isReady == YES │
│ • 线程条件: [[NSThread currentThread] name] == @"NetworkThread" │
│ • 崩溃条件: exception.name == @"NSInvalidArgumentException" │
└──────────────────────────────────────────────────────────────────────┘2.3 日志断点(Log Point)
日志断点(Log Point / 打印断点):
┌──────────────────────────────────────────────────────────────────────┐
│ 日志断点工作流程: │
│ │
│ 与条件断点的关键区别: │
│ • 条件断点:命中时暂停执行 │
│ • 日志断点:命中时打印信息,不停止执行 │
│ │
│ LLDB 实现方式: │
│ ┌──────────────────────────────────────────────────────────────┐ │
│ │ breakpoint set --name "cellForRowAt" │ │
│ │ --command "expression --swift (indexPath.row, section)"│ │
│ │ --stop-hook "continue" │ │
│ │ │ │
│ │ 命中时: │ │
│ │ 1. 触发断点 → LLDB 暂停 │ │
│ │ 2. 执行 command 列表(打印日志) │ │
│ │ 3. 自动 continue → 进程恢复 │ │
│ │ (整个过程对用户透明,无感知) │ │
│ └──────────────────────────────────────────────────────────────┘ │
│ │
│ 日志断点应用场景: │
│ • 高频函数调用统计(cellForRowAt、scrollViewDidScroll) │
│ • 网络请求参数记录 │
│ • 动画回调追踪 │
│ • 性能分析(不中断执行的情况下记录耗时) │
│ │
│ 性能对比: │
│ ┌───────────────┬──────────────┬──────────────┬──────────────────┐ │
│ │ 方式 │ 执行开销 │ 可生产使用 │ 适用场景 │ │
│ ├───────────────┼──────────────┼──────────────┼──────────────────┤ │
│ │ NSLog │ 高(I/O) │ ❌ 生产禁用 │ 快速调试 │ │
│ │ 日志断点 │ 中(仅命中时)│ ✅ 条件启用 │ 生产调试 │ │
│ │ #if DEBUG │ 零(编译期) │ ✅ 编译期剥离 │ 开发调试 │ │
│ └───────────────┴──────────────┴──────────────┴──────────────────┘ │
└──────────────────────────────────────────────────────────────────────┘2.4 观察断点(Watchpoint)
观察断点(Watchpoint)原理:
┌──────────────────────────────────────────────────────────────────────┐
│ 观察断点 vs 普通断点: │
│ │
│ 普通断点:代码地址触发 → 执行到某行时暂停 │
│ 观察断点:内存地址触发 → 访问某地址时暂停(读/写/读写) │
│ │
│ 硬件实现原理: │
│ ┌──────────────────────────────────────────────────────────────┐ │
│ │ ARM64 调试寄存器: │ │
│ │ │ │
│ │ DADRn (Debug Address Registers) — 存储监控地址 │ │
│ │ DACRn (Debug Access Control Registers) — 存储访问控制位 │ │
│ │ • bit[1:0]: 访问类型(1=写,2=读,3=读写) │ │
│ │ • bit[3:2]: 访问长度(1=1字节,2=2字节,3=4字节) │ │
│ │ • bit[5:4]: 访问权限(0=用户态,3=特权态) │ │
│ │ │ │
│ │ 最多支持 4 个硬件观察断点(ARM64) │ │
│ │ 超出时 LLDB 自动回退到"软件监控"(周期性读内存比较) │ │
│ └──────────────────────────────────────────────────────────────┘ │
│ │
│ LLDB 设置观察断点: │
│ watchpoint set variable myObject.count │
│ watchpoint set expression -- myView.frame.origin.x > 100 │
│ watchpoint set variable --type unsigned --size 4 *myPointer │
│ │
│ 观察断点的应用场景: │
│ • 追踪变量何时被修改(bug 定位) │
│ • 追踪内存泄漏(观察 dealloc 是否被调用) │
│ • 追踪布局属性何时被修改(AutoLayout bug) │
│ • 追踪动画属性何时被改变 │
└──────────────────────────────────────────────────────────────────────┘2.5 异常断点
异常断点(Exception Breakpoint):
┌──────────────────────────────────────────────────────────────────────┐
│ Objective-C 异常断点: │
│ │
│ 工作原理: │
│ ┌──────────────────────────────────────────────────────────────┐ │
│ │ NSException.raise │ │
│ │ ↓ │ │
│ │ objc_exception_throw() │ │
│ │ ↓ │ │
│ │ __cxa_throw() (底层 C++ 异常) │ │
│ │ ↓ │ │
│ │ 系统分发到 exception handler │ │
│ │ ↓ │ │
│ │ LLDB 监听异常事件 │ │
│ │ ↓ │ │
│ │ 命中异常断点 → 暂停进程 │ │
│ └──────────────────────────────────────────────────────────────┘ │
│ │
│ 配置方法: │
│ • Xcode Breakpoint Navigator → "+" → Add Exception Breakpoint │
│ • 可选:仅捕获特定异常(NSInvalidArgumentException 等) │
│ • 可选:仅捕获抛出时/捕获时 │
│ │
│ 异常断点类型: │
│ ┌───────────────┬──────────────────────────────┬─────────────────┐ │
│ │ 捕获时机 │ 行为 │ 适用场景 │ │
│ ├───────────────┼──────────────────────────────┼─────────────────┤ │
│ │ 抛出时 (C++/OC)│ 在异常抛出瞬间暂停 │ 精确捕获异常 │ │
│ │ 抛出时 (ObjC) │ 在 objc_exception_throw │ OC 异常定位 │ │
│ │ │ 之前暂停 │ │ │
│ │ 捕获时 │ 在 catch 块进入时暂停 │ 异常处理调试 │ │
│ │ 所有异常 │ 所有语言/类型 │ 全面捕获 │ │
│ └───────────────┴──────────────────────────────┴─────────────────┘ │
│ │
│ 常见异常及修复: │
│ • NSRangeException — 数组越界(NSRangeException) │
│ • NSInvalidArgumentException — 无效参数 │
│ • NSInternalInconsistencyException — 内部不一致 │
│ • unrecognizedSelector — 消息发送到不响应该方法对象 │
│ • EXC_BAD_ACCESS — 野指针(非 OC 异常,是 Mach 异常) │
└──────────────────────────────────────────────────────────────────────┘3. LLDB 命令详解
3.1 LLDB 核心架构
LLDB(Low Level Debugger)架构:
┌──────────────────────────────────────────────────────────────────────┐
│ LLDB 架构层次: │
│ │
│ ┌──────────────────────────────────────────────────────────────┐ │
│ │ Layer 1: 用户接口 (Command Line / GUI) │ │
│ │ ┌──────┐ ┌──────┐ ┌──────┐ ┌─────────┐ ┌───────────┐ │ │
│ │ │ CLI │ │ GDBMI│ │ SB │ │ Xcode │ │ LLDB- │ │ │
│ │ │ 命令行│ │ 协议 │ │ API │ │ 集成 │ │ Server │ │ │
│ │ └──────┘ └──────┘ └──────┘ └─────────┘ └───────────┘ │ │
│ └──────────────────────────────────────────────────────────────┘ │
│ ↓ │
│ ┌──────────────────────────────────────────────────────────────┐ │
│ │ Layer 2: 命令解析层 (Command Interpreter) │ │
│ │ • 解析输入命令 → 转换为内部命令对象 │ │
│ │ • 命令别名系统(command alias) │ │
│ │ • 命令补全(tab 补全) │ │
│ │ • 脚本扩展(Python) │ │
│ └──────────────────────────────────────────────────────────────┘ │
│ ↓ │
│ ┌──────────────────────────────────────────────────────────────┐ │
│ │ Layer 3: 服务层 (Service Layer) │ │
│ │ • Target — 目标进程/内核 │ │
│ │ • Process — 运行时进程状态 │ │
│ │ • Thread — 线程管理 │ │
│ │ • Frame — 堆栈帧操作 │ │
│ │ • Module — 二进制模块/镜像 │ │
│ │ • Symbol — 符号表操作 │ │
│ │ • SB API (Swift Bindings) │ │
│ └──────────────────────────────────────────────────────────────┘ │
│ ↓ │
│ ┌──────────────────────────────────────────────────────────────┐ │
│ │ Layer 4: 平台抽象层 (Platform Abstraction) │ │
│ │ • iOS (ARM64, Darwin kernel) │ │
│ │ • macOS (x86_64/ARM64, Darwin kernel) │ │
│ │ • Linux (via lldb-minidebugger) │ │
│ │ • Windows (via lldb-windbg) │ │
│ └──────────────────────────────────────────────────────────────┘ │
│ ↓ │
│ ┌──────────────────────────────────────────────────────────────┐ │
│ │ Layer 5: 内核交互层 (Kernel Interface) │ │
│ │ • ptrace (macOS/iOS 进程控制) │ │
│ │ • Mach Exception Port (异常处理) │ │
│ │ • kdebug trace (性能追踪) │ │
│ │ • dyld helper (动态链接器支持) │ │
│ └──────────────────────────────────────────────────────────────┘ │
└──────────────────────────────────────────────────────────────────────┘3.2 LLDB 命令分类速查表
LLDB 命令分类速查表:
┌───────────────┬──────────────────────────────────────────────────┐
│ 分类 │ 核心命令 │
├───────────────┼──────────────────────────────────────────────────┤
│ 断点管理 │ breakpoint / b / breakpoint list / bplist │
│ │ breakpoint delete / bdel │
│ │ breakpoint enable / disable │
│ │ breakpoint set / bs / breakpoint clear / bclr │
│ │ breakpoint command add │
│ │ breakpoint ignore │
│ 执行控制 │ continue / c / step / s / next / n / finish / f │
│ │ step-over / step-inst / step-inst-over │
│ │ thread step-over / step-inst-over │
│ │ thread step-inst-over │
│ │ jump / thread return │
│ 变量/表达式 │ po / p / expr / e / fr var / fr v / frame v │
│ │ expr -- / e -- │
│ │ register read / r / register write │
│ 内存操作 │ memory read / x / memory write / xm │
│ │ memory scan / memscan │
│ │ heap dump / heap query │
│ 线程/进程 │ thread list / thread select / thread backtrace │
│ │ process launch / attach / run / kill │
│ │ process status / process info │
│ 模块/镜像 │ image list / image lookup │
│ │ module list / target modules │
│ │ symbol lookup / image lookup --name │
│ 汇编操作 │ disassemble / di / disas │
│ │ register read --raw │
│ 系统信息 │ platform list / platform select │
│ │ version / version info │
│ 脚本/配置 │ command alias / script / source / script import │
│ │ settings set / settings show │
│ 观察点 │ watchpoint set / watchpoint list / wp │
│ │ watchpoint delete / wp del │
│ 符号文件 │ sbtarget download symbol-file │
│ │ sbtarget clear symbol-file │
└───────────────┴──────────────────────────────────────────────────┘4. LLDB 执行控制
4.1 执行控制命令详解
执行控制命令深度分析:
┌──────────────────────────────────────────────────────────────────────┐
│ 命令 │ 别名 │ 说明 │ 与 GDB 对比 │
├─────────────┼───────┼─────────────────────────────────┼───────────────────────┤
│ continue │ c │ 继续执行到下一个断点 │ GDB: continue │
│ step │ s │ 单步进入函数内部 │ GDB: step │
│ next │ n │ 单步跳过函数(不进入) │ GDB: next │
│ finish │ fi │ 执行到当前函数返回 │ GDB: finish │
│ step-over │ │ 等同于 next │ GDB: next │
│ step-inst │ │ 单步指令级(汇编) │ GDB: si │
│ step-inst- │ │ 单步指令级不进入 │ GDB: si │
│ over │ │ │ │
│ jump │ │ 跳转到指定行/地址(修改 PC) │ GDB: jump │
│ thread │ │ 线程级执行控制 │ GDB: thread │
│ return │ │ 直接返回当前函数(修改变量) │ GDB: return │
│ thread │ │ 线程列表 │ GDB: info thread │
│ list │ │ │ │
│ thread │ │ 选择线程 │ GDB: thread select │
│ select │ │ │ │
│ thread │ │ 当前线程 backtrace │ GDB: bt │
│ backtrace │ │ │ │
│ frame │ │ 切换堆栈帧 │ GDB: frame │
│ select │ │ │ │
│ frame │ │ 切换到第 N 帧 │ GDB: frame N │
│ up │ │ 上一帧(更靠近 main) │ GDB: up │
│ frame │ │ 下一帧(更远离 main) │ GDB: down │
│ down │ │ │ │
│ frame │ │ 获取当前帧信息 │ GDB: frame info │
│ info │ │ │ │
│ frame │ │ 列出当前帧变量 │ GDB: info locals │
│ variables │ │ │ │
└─────────────┴───────┴─────────────────────────────────┴───────────────────────┘
执行控制流程图:
普通函数调用栈:
┌──────────────────────────────────────────────────────────────┐
│ main() │
│ ┌─ viewDidLoad() ──────────────────────────────────────┐ │
│ │ ┌─ tableView(_:cellForRowAt:) ──────────────────┐ │ │
│ │ │ ┌─ configureCell(_:) ────────────────────┐ │ │ │
│ │ │ │ ┌─ loadImage(from:) ────────────────┐ │ │ │ │
│ │ │ │ │ (异步 - 不阻塞调试) │ │ │ │ │
│ │ │ │ └──────────────────────────────────┘ │ │ │ │
│ │ │ └─────────────────────────────────────┘ │ │ │
│ │ └─────────────────────────────────────────┘ │ │ │
│ └─────────────────────────────────────────────┘ │ │
└──────────────────────────────────────────────────────────────┘
step (s) 追踪路径:
main() → viewDidLoad() → tableView() → configureCell() → loadImage()
next (n) 追踪路径:
main() → viewDidLoad() → tableView() → configureCell() → loadImage()
(整个函数当作一步)
finish (fi) 追踪路径:
main() → viewDidLoad() → tableView() → [返回] → configureCell() → [返回]4.2 线程控制详解
多线程调试详解:
┌──────────────────────────────────────────────────────────────────────┐
│ 多线程调试关键命令: │
│ │
│ ┌────────────────────────────────────────────────────────────────┐ │
│ │ 线程管理命令: │ │
│ ├────────────────────────────────────────────────────────────────┤ │
│ │ thread list → 列出所有线程 │ │
│ │ thread info <n> → 查看线程 n 详情 │ │
│ │ thread select <n> → 切换到线程 n │ │
│ │ thread backtrace <n> → 线程 n 的调用栈 │ │
│ │ thread backtrace all → 所有线程的调用栈 │ │
│ │ process status → 进程状态 │ │
│ │ process continue --all → 所有线程继续 │ │
│ │ process continue --stop-others → 仅当前线程继续 │ │
│ │ thread step-over --threads all → 所有线程单步过 │ │
│ │ thread step-inst-over --threads all → 所有线程指令单步过 │ │
│ └────────────────────────────────────────────────────────────────┘ │
│ │
│ 多线程调试策略: │
│ ┌────────────────────────────────────────────────────────────────┐ │
│ │ 策略 │ 方法 │ 适用场景 │ │
│ ├────────────────────────────────────────────────────────────────┤ │
│ │ 全部停止 │ process continue --stop-others │ 全局调试 │ │
│ │ 线程暂停 │ thread schedule │ 观察调度 │ │
│ │ 跨线程追踪 │ thread backtrace all │ 关系分析 │ │
│ │ 线程暂停 │ thread stop │ 独立调试 │ │
│ │ 死锁检测 │ thread backtrace all + lock 分析 │ 死锁定位 │ │
│ └────────────────────────────────────────────────────────────────┘ │
│ │
│ 常见多线程调试场景: │
│ ┌────────────────────────────────────────────────────────────────┐ │
│ │ 场景 │ 调试方法 │ │
│ ├────────────────────────────────────────────────────────────────┤ │
│ │ 数据竞争 │ TSan + thread backtrace all │ │
│ │ 死锁 │ thread list + thread info (查看 lock 状态) │ │
│ │ 线程泄漏 │ thread list + 对比快照 │ │
│ │ 线程调度 │ thread step-over --threads all │ │
│ │ GCD 队列 │ breakpoint set --name "dispatch_async" │ │
│ │ 主线程阻塞 │ 检查主线程 backtrace(tid=0x1) │ │
│ │ 后台线程崩溃 │ thread backtrace all + 检查非主线程 │ │
│ └────────────────────────────────────────────────────────────────┘ │
└──────────────────────────────────────────────────────────────────────┘5. LLDB 内存与变量操作
5.1 变量查看与表达式求值
变量查看和表达式求值完整指南:
┌──────────────────────────────────────────────────────────────────────┐
│ 变量查看命令对比: │
│ │
│ ┌─────────┬──────────────────────────┬───────────────────────────┐ │
│ │ 命令 │ 功能 │ 输出示例 │ │
│ ├─────────┼──────────────────────────┼───────────────────────────┤ │
│ │ po │ Objective-C 对象(调 description) │ <UIView: 0x600003fa0> │ │
│ │ │ → 调用 -description 或 │ <MyModel: 0x600003f20> │ │
│ │ │ Swift's description │ 属性: 值 │ │
│ │ p │ C/基本类型值 │ (int) $0 = 42 │ │
│ │ │ → 直接值输出 │ (float) $1 = 3.14 │ │
│ │ e │ 执行表达式(求值+修改) │ (void) │ │
│ │ │ → 可修改变量 │ (void) $2 = 0.5 │ │
│ │ fr var │ 当前帧所有变量 │ (int) _count = 42 │ │
│ │ │ → 局部变量 + 参数 │ (NSString *) _name = ... │ │
│ │ frame v │ 同上(短别名) │ │ │
│ │ v │ 实例变量(ivar) │ (id) $3 = @"hello" │ │
│ │ ivar │ │ │ │
│ │ register│ 寄存器状态 │ (uint64_t) $x0 = 0x... │ │
│ │ read │ │ (uint64_t) $x1 = 0x... │ │
│ └─────────┴──────────────────────────┴───────────────────────────┘ │
│ │
│ 表达式求值的高级用法: │
│ ┌────────────────────────────────────────────────────────────────┐ │
│ │ Swift 表达式求值: │ │
│ │ • e self.view.alpha = 0.5 → 运行时修改 UI │ │
│ │ • e self.data.append("new") → 运行时添加数据 │ │
│ │ • e print("debug: \(self.name)") → 运行时打印 │ │
│ │ • e self.tableView.reloadData() → 运行时刷新 │ │
│ │ • e self.navigationController?.popViewController(animated: true)│ │
│ │ │ │
│ │ Objective-C 表达式求值: │ │
│ │ • e (void)[self.tableView reloadData] │ │
│ │ • e (NSString *)[self valueForKey:@"name"] │ │
│ │ • e (CGRect)[self.view convertRect:self.view.frame │ │
│ │ toView:nil] │ │
│ │ │ │
│ │ 跨语言求值注意事项: │ │
│ │ • Swift 表达式用 --swift 前缀:e --swift self.count │ │
│ │ • OC 表达式用 (void) 等类型前缀 │ │
│ │ • 表达式可以修改运行时状态(危险但强大) │ │
│ │ • 表达式在调试器上下文中执行,不是原进程的上下文 │ │
│ └────────────────────────────────────────────────────────────────┘ │
└──────────────────────────────────────────────────────────────────────┘5.2 内存查看与修改
内存查看与修改详解:
┌──────────────────────────────────────────────────────────────────────┐
│ 内存查看命令: │
│ │
│ ┌─────────┬──────────────────────────┬───────────────────────────┐ │
│ │ 命令 │ 功能 │ 示例 │ │
│ ├─────────┼──────────────────────────┼───────────────────────────┤ │
│ │ memory │ 读取指定地址内存 │ x 0x600003fa0000 16 │ │
│ │ read │ (hex 格式输出) │ x/16xb 0x... │ │
│ │ │ │ x/16xw 0x... │ │
│ │ │ │ x/16xd 0x... │ │
│ │ │ │ x/16hx 0x... │ │
│ │ memory │ 写入内存 │ xm 0x600003fa0000 42 │ │
│ │ write │ │ xmw 0x600003fa0000 0xff │ │
│ │ memscan │ 内存扫描 │ memscan 0x600000000000 │ │
│ │ │ │ 1024 "test" │ │
│ │ heap │ 堆转储 │ heap dump │
│ │ dump │ │ │
│ │ heap │ 堆查询 │ heap query NSObject │
│ │ query │ │ │
│ │ class │ 类信息 │ class dump MyClass │
│ │ dump │ │ │
│ │ property│ 属性列表 │ property list MyClass │
│ │ list │ │ │
│ │ method │ 方法列表 │ method list MyClass │
│ │ list │ │ │
│ └─────────┴──────────────────────────┴───────────────────────────┘ │
│ │
│ memory read 输出格式说明: │
│ ┌────────────────────────────────────────────────────────────────┐ │
│ │ 格式符 │ 含义 │ 示例输出 │ │
│ ├────────────────────────────────────────────────────────────────┤ │
│ │ x │ 十六进制 │ 0x7fff5fbff8e4 │ │
│ │ d │ 十进制有符号 │ 140735162879876 │ │
│ │ u │ 十进制无符号 │ 140735162879876 │ │
│ │ f │ 浮点数 │ 3.141592653 │ │
│ │ a │ 地址 │ 0x7fff5fbff8e4 │ │
│ │ c │ 字符 │ 'A' │ │
│ │ s │ 字符串 │ "Hello" │ │
│ │ i │ 汇编指令 │ push %rbp │ │
│ │ b │ 单字节 (byte) │ 0x41 │ │
│ │ h │ 双字节 (half) │ 0x1234 │ │
│ │ w │ 四字节 (word) │ 0x12345678 │ │
│ │ g │ 八字节 (giant) │ 0x123456789abcdef0 │ │
│ │ n │ 每 n 字节换行 │ x/16xb (每字节一行) │ │
│ └────────────────────────────────────────────────────────────────┘ │
│ │
│ 内存扫描实用示例: │
│ ┌────────────────────────────────────────────────────────────────┐ │
│ │ 搜索特定字符串: │ │
│ │ memscan 0x600000000000 1024 "test" │ │
│ │ │ │
│ │ 搜索对象地址: │ │
│ │ memscan 0x600000000000 1024 myObjectAddress │ │
│ │ │ │
│ │ 搜索特定模式(整数): │ │
│ │ memscan 0x600000000000 1024 0x12345678 │ │
│ └────────────────────────────────────────────────────────────────┘ │
└──────────────────────────────────────────────────────────────────────┘5.3 类、属性、方法信息查看
类/属性/方法信息查询:
┌──────────────────────────────────────────────────────────────────────┐
│ 类信息查询(class dump): │
│ │
│ LLDB 类信息查询命令: │
│ ┌────────────────────────────────────────────────────────────────┐ │
│ │ 命令 │ 功能 │ │
│ ├────────────────────────────────────────────────────────────────┤ │
│ │ class dump MyClass │ 打印类所有信息 │ │
│ │ property list MyClass │ 打印属性列表 │ │
│ │ method list MyClass │ 打印方法列表 │ │
│ │ class dump -c MyClass │ 类信息摘要 │ │
│ │ property list MyClass -i │ 实例属性 │ │
│ │ property list MyClass -c │ 类属性 │ │
│ │ method list MyClass -i │ 实例方法 │ │
│ │ method list MyClass -c │ 类方法 │ │
│ │ property list MyClass -p │ 带属性修饰符 │ │
│ │ method list MyClass -t │ 带类型签名 │ │
│ │ property list MyClass -v │ 带变量信息 │ │
│ └────────────────────────────────────────────────────────────────┘ │
│ │
│ 输出示例: │
│ ┌────────────────────────────────────────────────────────────────┐ │
│ │ class dump MyClass │ │
│ │ ┌─────────────────────────────────────────────────────────┐ │ │
│ │ │ @interface MyClass : NSObject │ │ │
│ │ │ @property (nonatomic, strong) NSString *name; │ │ │
│ │ │ @property (nonatomic, assign) NSInteger count; │ │ │
│ │ │ - (void)doSomething; │ │ │
│ │ │ + (instancetype)sharedInstance; │ │ │
│ │ │ @end │ │ │
│ │ └─────────────────────────────────────────────────────────┘ │ │
│ └────────────────────────────────────────────────────────────────┘ │
│ │
│ image lookup 地址解析: │
│ ┌────────────────────────────────────────────────────────────────┐ │
│ │ image lookup --address 0x12345678 │ │
│ │ // 输出: │ │
│ │ Address: MyApp[0x1234] (MyApp.framework.MyClass.method) │ │
│ │ Summary: MyClass::method() │ │
│ │ Module: file = "MyApp.framework" │ │
│ │ base addr = 0x100000000 │ │
│ │ CompileUnit: id = 0x0, file = "MyClass.swift", │ │
│ │ language = "Swift" │ │
│ │ Function: id = 0x0, name = "MyClass.method()", │ │
│ │ file = "MyClass.swift", │ │
│ │ begin = 0x1234, end = 0x1250 │ │
│ │ Lines: #10 @"method()" │ │
│ └────────────────────────────────────────────────────────────────┘ │
└──────────────────────────────────────────────────────────────────────┘6. LLDB 高级特性
6.1 动态断点(Dynamic Breakpoint)
动态断点(Dynamic Breakpoint):
┌──────────────────────────────────────────────────────────────────────┐
│ 动态断点 vs 静态断点: │
│ │
│ ┌─────────────────┬──────────────────────┬───────────────────────┐ │
│ │ 特性 │ 动态断点 │ 静态断点 │ │
│ ├─────────────────┼──────────────────────┼───────────────────────┤ │
│ │ 设置时机 │ 运行时动态插入 │ 运行前设置 │ │
│ │ 代码修改 │ 需要动态代码注入 │ 断点处修改 │ │
│ │ 适用场景 │ 拦截无法提前设置断点 │ 已知代码位置 │ │
│ │ │ 的代码 │ │ │
│ │ 性能开销 │ 高(动态编译) │ 低(仅 SIGTRAP) │ │
│ │ 跨语言支持 │ C/C++/OC │ 所有语言 │ │
│ │ Swift 支持 │ ❌ │ ✅ │ │
│ │ 需要 root │ ❌(LLDB 自动处理) │ ❌ │ │
│ └─────────────────┴──────────────────────┴───────────────────────┘ │
│ │
│ 动态断点设置: │
│ ┌────────────────────────────────────────────────────────────────┐ │
│ │ 拦截特定函数进入: │ │
│ │ breakpoint set --name "pushViewController:animated:" │ │
│ │ 拦截函数返回: │ │
│ │ breakpoint set --name "pushViewController:animated:" │ │
│ │ --kind "function-return" │ │
│ │ 拦截方法进入+返回: │ │
│ │ breakpoint set --name "tableView:cellForRowAtIndexPath" │ │
│ │ --one-shote │ │
│ │ 拦截类的所有方法: │ │
│ │ breakpoint set --class MyClass │ │
│ │ 拦截模块中所有符号: │ │
│ │ breakpoint set --module "MyApp" │ │
│ │ 拦截 dyld 动态加载的符号: │ │
│ │ breakpoint set --func-regex "loadLibrary.*" │ │
│ └────────────────────────────────────────────────────────────────┘ │
└──────────────────────────────────────────────────────────────────────┘6.2 符号断点与库加载断点
符号断点与库加载断点:
┌──────────────────────────────────────────────────────────────────────┐
│ 符号断点(Symbol Breakpoint): │
│ │
│ ┌────────────────────────────────────────────────────────────────┐ │
│ │ 设置方式: │ │
│ │ breakpoint set --name "UIApplicationMain" │ │
│ │ breakpoint set --name "*-[UIViewController viewDidLoad]" │ │
│ │ breakpoint set --func-regex ".*ViewController.*" │ │
│ │ breakpoint set --module "UIKit" --name "pushViewController" │ │
│ │ │ │
│ │ 应用场景: │ │
│ │ • 拦截系统框架方法(如 UIKit 内部调用) │ │
│ │ • 拦截第三方库方法 │ │
│ │ • 拦截所有 ViewController 生命周期方法 │ │
│ │ • 拦截所有网络请求方法 │ │
│ └────────────────────────────────────────────────────────────────┘ │
│ │
│ 库加载断点(Library Load Breakpoint): │
│ ┌────────────────────────────────────────────────────────────────┐ │
│ │ 设置方式: │ │
│ │ breakpoint set --library "libswiftCore.dylib" │ │
│ │ breakpoint set --library "libsystem*" │ │
│ │ breakpoint set --library "MyCustomFramework" │ │
│ │ breakpoint set --library-load "MyCustomFramework" │ │
│ │ │ │
│ │ 应用场景: │ │
│ │ • 追踪库加载时机和顺序 │ │
│ │ • 分析库初始化时间 │ │
│ │ • 查找重复加载的库 │ │
│ │ • 分析库冲突问题 │ │
│ └────────────────────────────────────────────────────────────────┘ │
│ │
│ 库加载断点工作流程: │
│ ┌────────────────────────────────────────────────────────────────┐ │
│ │ 进程启动 │ │
│ │ ↓ │ │
│ │ dyld 加载主二进制文件 │ │
│ │ ↓ │ │
│ │ dyld 加载依赖库(按依赖顺序) │ │
│ │ ↓ │ │
│ │ 命中库加载断点 → LLDB 暂停 │ │
│ │ ↓ │ │
│ │ 可检查加载的库: │ │
│ │ image list → 列出所有加载的镜像 │ │
│ │ image lookup --address <addr> → 查找地址所在的镜像 │ │
│ └────────────────────────────────────────────────────────────────┘ │
└──────────────────────────────────────────────────────────────────────┘6.3 断点条件与操作
断点条件与操作(Breakpoint Conditions & Actions):
┌──────────────────────────────────────────────────────────────────────┐
│ 断点操作类型: │
│ │
│ ┌─────────────────┬──────────────────────────────────────────────┐ │
│ │ 类型 │ 说明 │ │
│ ├─────────────────┼──────────────────────────────────────────────┤ │
│ │ 暂停(默认) │ 命中时暂停进程,显示上下文 │ │
│ │ 打印日志 │ 命中时执行命令(如 po/p),不停止 │ │
│ │ 忽略 N 次 │ 前 N 次命中忽略,第 N+1 次暂停 │ │
│ │ 执行脚本 │ 命中时执行 Python 脚本 │ │
│ │ 记录日志到文件 │ 命中时写入指定文件 │ │
│ │ 条件暂停 │ 满足条件时才暂停 │ │
│ │ 连续断点 │ 一次设置多个位置 │ │
│ └─────────────────┴──────────────────────────────────────────────┘ │
│ │
│ 复杂断点配置示例: │
│ ┌────────────────────────────────────────────────────────────────┐ │
│ │ 1. 条件 + 日志断点: │ │
│ │ breakpoint set --name "cellForRowAt" │ │
│ │ --condition "indexPath.row > 10" │ │
│ │ --command "log" │ │
│ │ │ │
│ │ 2. 忽略 N 次后暂停: │ │
│ │ breakpoint set --name "scrollViewDidScroll" │ │
│ │ --ignore 99 │ │ │
│ │ // 前 99 次忽略,第 100 次暂停 │ │
│ │ │ │
│ │ 3. 断点命令列表: │ │
│ │ breakpoint command add 1 │ │
│ │ > expression --swift print("Debug: \\(self.count)") │ │
│ │ > po self.tableView │ │
│ │ > expr self.tableView.reloadData() │ │
│ │ > continue │ │
│ │ > end │ │
│ │ │ │
│ │ 4. 线程特定断点: │ │
│ │ breakpoint set --name "doSomething" │ │
│ │ --thread 3 │ │
│ │ │ │
│ │ 5. 模块特定断点: │ │
│ │ breakpoint set --module "Alamofire" --name "request" │ │
│ └────────────────────────────────────────────────────────────────┘ │
└──────────────────────────────────────────────────────────────────────┘7. LLDB 脚本与自定义命令
7.1 LLDB 配置文件
LLDB 配置文件与自定义:
┌──────────────────────────────────────────────────────────────────────┐
│ 配置文件位置: │
│ ┌────────────────────────────────────────────────────────────────┐ │
│ │ 用户配置: ~/.lldbinit │ │
│ │ 项目配置: .lldbinit (项目根目录) │ │
│ │ Xcode 配置:~/Library/Developer/Xcode/UserData/LLDB/ │ │
│ └────────────────────────────────────────────────────────────────┘ │
│ │
│ 常用 .lldbinit 配置: │
│ ┌────────────────────────────────────────────────────────────────┐ │
│ │ # 彩色 LLDB 输出 │ │
│ │ command alias colors script import lldb_colors │ │
│ │ colors on │ │
│ │ │ │
│ │ # 自定义别名 │ │
│ │ command alias ps "frame variable" │ │
│ │ command alias pb "breakpoint list" │ │
│ │ command alias pc "process status" │ │
│ │ command alias pt "thread backtrace all" │ │
│ │ command alias ppo "po" │ │
│ │ │ │
│ │ # 自动 rebase(跨平台调试) │ │
│ │ command script install lldb_rebase.py │ │
│ │ │ │
│ │ # Swift 调试增强 │ │
│ │ settings set target.skip-simlib true │ │
│ │ settings set target.load-cstr-common-strings true │ │
│ │ │ │
│ │ # 断点颜色配置 │ │
│ │ breakpoint color set 1 red │ │
│ │ breakpoint color set 2 green │ │
│ │ breakpoint color set 3 blue │ │
│ └────────────────────────────────────────────────────────────────┘ │
└──────────────────────────────────────────────────────────────────────┘7.2 LLDB Python 脚本扩展
LLDB Python 脚本扩展:
┌──────────────────────────────────────────────────────────────────────┐
│ LLDB 脚本扩展机制: │
│ │
│ ┌────────────────────────────────────────────────────────────────┐ │
│ │ 脚本注册方式: │ │
│ │ │ │
│ │ 方式 1: Python 文件 (.py) │ │
│ │ command script import /path/to/script.py │ │
│ │ │ │
│ │ 方式 2: .lldbinit 中 inline │ │
│ │ command script add -f my_module.my_function my_command │ │
│ │ │ │
│ │ 方式 3: Xcode 插件 │ │
│ │ ~/Library/Developer/Xcode/UserData/LLDB/Support/ │ │
│ └────────────────────────────────────────────────────────────────┘ │
│ │
│ 实用 Python 脚本示例: │
│ ┌────────────────────────────────────────────────────────────────┐ │
│ │ #!/usr/bin/env python3 │ │
│ │ # lldb_expression.py │ │
│ │ │ │
│ │ import lldb │ │
│ │ │ │
│ │ def swift_print(debugger, command, result, _): │ │
│ │ '''Swift print 命令''' │ │
│ │ target = debugger.GetSelectedTarget() │ │
│ │ process = target.GetProcess() │ │
│ │ thread = process.GetSelectedThread() │ │
│ │ frame = thread.GetSelectedFrame() │ │
│ │ expr_result = frame.EvaluateExpression( │ │
│ │ f"print({command})", │ │
│ │ options=lldb.SBExpressionOptions() │ │
│ │ ) │ │
│ │ result.AppendMessage(expr_result.GetError()) │ │
│ │ │ │
│ │ def dump_all_ivars(debugger, command, result, _): │ │
│ │ '''Dump all ivars of current object''' │ │
│ │ target = debugger.GetSelectedTarget() │ │
│ │ process = target.GetProcess() │ │
│ │ thread = process.GetSelectedThread() │ │
│ │ frame = thread.GetSelectedFrame() │ │
│ │ obj = frame.EvaluateExpression("self").GetObject(); │ │
│ │ if obj.IsValid(): │ │
│ │ cls_name = obj.GetType().GetName() │ │
│ │ for i in range(100): # 假设最多 100 个 ivar │ │
│ │ ivar = frame.EvaluateExpression( │ │
│ │ f"*(id *)(((char *){obj}) + {i*8})") │ │
│ │ if ivar.IsValid() and ivar.GetValueAsLong() != 0: │ │
│ │ result.AppendMessage(f" ivar[{i}]: {ivar}") │ │
│ │ │ │
│ │ def __lldb_init_module(debugger, _): │ │
│ │ debugger.HandleCommand( │ │
│ │ "command script add -f swift_print swprint") │ │
│ │ debugger.HandleCommand( │ │
│ │ "command script add -f dump_all_ivars dump_ivars") │ │
│ │ print("LLDB scripts loaded!") │ │
│ └────────────────────────────────────────────────────────────────┘ │
│ │
│ 常用 LLDB Python 脚本库: │
│ ┌────────────────────────────────────────────────────────────────┐ │
│ │ • lldbexpr — Python 表达式解析工具 │ │
│ │ • lldbplatform — 平台相关操作 │ │
│ │ • lldbutil — 通用调试工具 │ │
│ │ • SB API — Target/Process/Thread/Frame/Symbol 的 Python 绑定 │ │
│ │ • SwiftSupport — Swift 类型信息 │ │
│ │ • ObjCSupport — Objective-C 运行时支持 │ │
│ └────────────────────────────────────────────────────────────────┘ │
└──────────────────────────────────────────────────────────────────────┘7.3 LLDB 实用技巧
LLDB 实用技巧汇总:
┌──────────────────────────────────────────────────────────────────────┐
│ 实用技巧 1:修改变量(运行时热修复) │
│ ┌────────────────────────────────────────────────────────────────┐ │
│ │ // 在断点处修改 UI 属性(无需重新编译): │ │
│ │ e -- swift self.view.backgroundColor = .red │ │
│ │ e -- swift self.label.text = "Modified!" │ │
│ │ e -- swift self.tableView.reloadData() │ │
│ │ │ │
│ │ // 修改数据(无需重新生成): │ │
│ │ e -- swift self.data.append("Hot Fix") │ │
│ │ e -- swift self.users[0].name = "Patched Name" │ │
│ └────────────────────────────────────────────────────────────────┘ │
│ │
│ 实用技巧 2:检查调用栈(快速定位问题) │
│ ┌────────────────────────────────────────────────────────────────┐ │
│ │ // 查看当前调用栈 │ │
│ │ bt → backtrace │ │
│ │ thread backtrace → 当前线程调用栈 │ │
│ │ thread backtrace all → 所有线程调用栈 │ │
│ │ frame info → 当前帧信息 │ │
│ │ frame variable → 当前帧变量 │ │
│ │ register read → 寄存器值 │ │
│ └────────────────────────────────────────────────────────────────┘ │
│ │
│ 实用技巧 3:内存调试 │
│ ┌────────────────────────────────────────────────────────────────┐ │
│ │ // 查看对象内存布局 │ │
│ │ ivar save propertyName → 获取 ivar 地址 │ │
│ │ memory read <addr> → 读取内存内容 │ │
│ │ heap dump → 堆快照 │ │
│ │ heap query ClassName → 查询对象实例 │ │
│ │ memscan <addr> <size> <value> → 搜索内存 │ │
│ └────────────────────────────────────────────────────────────────┘ │
│ │
│ 实用技巧 4:调试技巧 │
│ ┌────────────────────────────────────────────────────────────────┐ │
│ │ // 动态插入断点 │ │
│ │ breakpoint set --name "newFunction" │ │
│ │ │ │
│ │ // 临时修改变量值 │ │
│ │ e -- swift myVar = newValue │ │
│ │ │ │
│ │ // 执行方法但不修改状态 │ │
│ │ e -- swift _ = myMethod() │ │
│ │ │ │
│ │ // 查看运行时类信息 │ │
│ │ po [obj class] │ │
│ │ po [obj class] superclass │ │
│ │ property list MyClass │ │
│ │ method list MyClass │ │
│ └────────────────────────────────────────────────────────────────┘ │
└──────────────────────────────────────────────────────────────────────┘8. Instruments 工具链
8.1 Instruments 工具全景
Instruments 工具链全景:
┌──────────────────────────────────────────────────────────────────────┐
│ │
│ Instruments 工具分类: │
│ │
│ ┌──────────────────┬────────────────────────────────────────────┐ │
│ │ 性能分析类 │ 内存分析类 │ 系统级工具 │ 安全工具 │ │
│ ├──────────────────┼────────────────────────────────────────────┤ │
│ │ • Time Profiler │ • Allocations │ • System Trace │ ASan │ │
│ │ • Allocations │ • Leaks │ • Energy Log │ TSan │ │
│ │ • Core Animation │ • Zombies │ • File Activity│ UBSan │ │
│ │ • GPU Profiler │ • Activity Mon. │ • Network │ Leak- │ │
│ │ • Metal Shader │ • VM Tracker │ • Time Prof. │ Canary │ │
│ │ • Thread Activ. │ • ObjectAlloc │ • Disk Utility │ │ │
│ └──────────────────┴────────────────────────────────────────────┘ │
│ │
│ 各工具详细对比: │
│ ┌────────────────┬───────────┬───────────┬───────────┬─────────┐ │
│ │ 工具 │ 检测内容 │ 开销 │ 实时性 │ 输出 │ │
│ ├────────────────┼───────────┼───────────┼───────────┼─────────┤ │
│ │ Time Profiler │ CPU 时间 │ 低 │ 实时 │ 火焰图 │ │
│ │ Allocations │ 内存分配 │ 中 │ 实时 │ 图表+表 │ │
│ │ Leaks │ 内存泄漏 │ 低 │ 实时 │ 列表 │ │
│ │ Zombies │ 野指针 │ 高 │ 实时 │ 异常 │ │
│ │ Core Anim. │ 渲染性能 │ 中 │ 实时 │ 帧图 │ │
│ │ Network │ 网络流量 │ 低 │ 实时 │ 请求列表│ │
│ │ Energy Log │ 能耗 │ 低 │ 实时 │ 图表 │ │
│ │ System Trace │ 系统调用 │ 中 │ 离线 │ 追踪图 │ │
│ │ Metal Shader │ GPU 着色 │ 中 │ 实时 │ 着色列表│ │
│ │ File Activity │ 文件 I/O │ 低 │ 实时 │ 文件列表│ │
│ │ GPU Profiler │ GPU 管线 │ 中 │ 实时 │ 帧分析 │ │
│ │ Thread Activ. │ 线程生命 │ 低 │ 实时 │ 线程图 │ │
│ │ Activity Mon. │ 进程活动 │ 低 │ 实时 │ 列表 │ │
│ │ VM Tracker │ 虚拟内存 │ 低 │ 实时 │ 图表 │ │
│ │ ObjectAlloc │ Obj-C 对象 │ 低 │ 实时 │ 对象图 │ │
│ └────────────────┴───────────┴───────────┴───────────┴─────────┘ │
└──────────────────────────────────────────────────────────────────────┘8.2 Instruments 使用流程
Instruments 标准使用流程:
┌──────────────────────────────────────────────────────────────────────┐
│ 完整工作流程: │
│ │
│ 1. 启动 Instruments │
│ Xcode → Product → Profile (⌘ + I) │
│ 或 open-instruments MyApp.app │
│ │
│ 2. 选择目标设备 │
│ • Device(真机)→ USB 连接,更精确 │
│ • Simulator(模拟器)→ 快速测试,性能有差异 │
│ │
│ 3. 选择分析工具 │
│ • 性能分析 → Time Profiler │
│ • 内存分析 → Allocations / Leaks │
│ • 渲染分析 → Core Animation │
│ • 网络分析 → Network │
│ • 能耗分析 → Energy Log │
│ • 系统追踪 → System Trace │
│ │
│ 4. 执行测试场景 │
│ • 模拟用户操作流程 │
│ • 覆盖关键路径 │
│ • 包含异常情况 │
│ │
│ 5. 分析结果 │
│ • 查看图表变化 │
│ • 定位热点函数 │
│ • 检查内存增长 │
│ • 分析调用栈 │
│ │
│ 6. 优化并验证 │
│ • 修改代码 │
│ • 重新 Profile → 对比结果 │
│ │
│ 快捷键: │
│ ┌──────────────┬─────────────────────────────────────────────────┐ │
│ │ ⌘ + R │ 开始/停止录制 │ │
│ │ ⌘ + S │ 创建快照(Snapshot) │ │
│ │ ⌘ + E │ 退出 Instruments │ │
│ │ ⌘ + 数字 │ 快速切换工具 │ │
│ │ 0-9 │ 切换左侧工具列表 │ │
│ │ ⌘ + Option + │ 显示/隐藏时间标尺 │ │
│ │ + 0 │ │ │
│ └──────────────┴─────────────────────────────────────────────────┘ │
└──────────────────────────────────────────────────────────────────────┘8.3 Instruments 工具深度对比
Instruments 工具深度对比表:
┌──────────────────┬──────────────┬──────────────┬──────────────┬──────────────┐
│ 维度 │ Time Prof │ Allocations │ Leaks │ Zombies │
│ │ iler │ │ │ │
├──────────────────┼──────────────┼──────────────┼──────────────┼──────────────┤
│ 核心用途 │ CPU 性能分析 │ 内存分配追踪 │ 内存泄漏检测 │ 野指针检测 │
│ 检测机制 │ 采样(1kHz) │ 内存hook │ 标记-清除 │ 对象替换 │
│ 检测内容 │ 函数耗时 │ 分配/释放 │ 不可达内存 │ 释放后访问 │
│ 实时性 │ 实时 │ 实时 │ 实时 │ 实时 │
│ 性能开销 │ 低(采样) │ 中 │ 低 │ 高(替换) │
│ 主要指标 │ 样本数/耗时 │ Live Bytes │ 泄漏字节数 │ 僵尸对象数 │
│ │ │ Allocs │ Allocs/Refs │ 异常位置 │
│ │ │ Bytes │ │ │
│ │ │ Retained │ │ │
│ │ │ Size │ │ │
│ 适用场景 │ 卡顿分析 │ 内存增长 │ 内存泄漏 │ 野指针崩溃 │
│ │ 热点函数 │ 内存峰值 │ 循环引用 │ 崩溃定位 │
│ │ 调用链分析 │ 分配频率 │ 资源未释放 │ 访问越界 │
│ 输出可视化 │ 火焰图 │ 内存柱状图 │ 泄漏列表 │ 崩溃位置 │
│ │ + 调用树 │ + 分配树 │ + 调用栈 │ + 异常信息 │
│ 与其他工具关系 │ 配合 │ 配合 Leaks │ 配合 │ 配合 │
│ │ Allocations │ 使用 │ Allocations │ Allocations │
│ 常见误报 │ 系统调用 │ 真实泄漏 │ 暂存内存 │ 正常 │
│ │ (可过滤) │ (需分析) │ (需排除) │ 使用 │
└──────────────────┴──────────────┴──────────────┴──────────────┴──────────────┘9. Time Profiler 深度分析
9.1 Time Profiler 原理
Time Profiler 采样原理深度分析:
┌──────────────────────────────────────────────────────────────────────┐
│ Time Profiler 采样机制: │
│ │
│ ┌────────────────────────────────────────────────────────────────┐ │
│ │ 采样原理: │ │
│ │ │ │
│ │ 1. LLDB/Instruments 使用 ptrace + mach 异常端口 │ │
│ │ 2. 每隔固定间隔(默认 1ms = 1000Hz)发送 SIGPROF 信号 │ │
│ │ 3. 内核暂停进程,保存当前寄存器状态 │ │
│ │ 4. 通过 symbolication 将地址映射到函数名 │ │
│ │ 5. 将调用栈入栈到采样树 │ │
│ │ 6. 恢复进程继续执行 │ │
│ │ │ │
│ │ 采样频率与精度: │ │
│ │ ┌───────────┬────────────────┬───────────────┬────────────┐ │ │
│ │ │ 采样率 │ 精度 │ 开销 │ 适用场景 │ │ │
│ │ ├───────────┼────────────────┼───────────────┼────────────┤ │ │
│ │ │ 100Hz │ 粗粒度 │ 极低 │ 概览 │ │ │
│ │ │ 1000Hz │ 标准(默认) │ 低 │ 一般分析 │ │ │
│ │ │ 4000Hz │ 精细 │ 中 │ 精细分析 │ │ │
│ │ │ 10000Hz │ 极高 │ 高 │ 极端精细 │ │ │
│ │ └───────────┴────────────────┴───────────────┴────────────┘ │ │
│ │ │ │
│ │ 采样 vs 插桩(Instrumentation)对比: │ │
│ │ ┌──────────────┬──────────────┬──────────────┬────────────┐ │ │
│ │ │ 特性 │ 采样(Sampling) │ 插桩(Instrumentation) │ │ │
│ │ ├──────────────┼──────────────┼──────────────┼────────────┤ │ │
│ │ │ 性能开销 │ 低 │ 高(每函数) │ │ │ │
│ │ │ 精度 │ 近似(统计) │ 精确(每调用) │ │ │ │
│ │ │ 代码修改 │ 无 │ 需要 │ │ │ │
│ │ │ 适用场景 │ 生产环境 │ 开发调试 │ │ │ │
│ │ │ 支持语言 │ C/C++/汇编 │ Swift/OC/ASM │ │ │ │
│ │ │ Swift支持 │ ✅ 符号化 │ ❌ 开销大 │ │ │ │
│ │ │ 异步调用 │ ✅ 自动追踪 │ 需手动追踪 │ │ │ │
│ │ └──────────────┴──────────────┴──────────────┴────────────┘ │ │
│ └────────────────────────────────────────────────────────────────┘ │
│ │
│ 采样数据的符号化过程: │
│ ┌────────────────────────────────────────────────────────────────┐ │
│ │ 原始地址 (0x1004AB123) → 无符号化 │ │
│ │ ↓ 符号化 (symbolicate) │ │
│ │ 函数名 (MyApp.MyClass.doSomething() + 0x42) │ │
│ │ ↓ 文件行号 │ │
│ │ MyApp.swift:42 │ │
│ │ │ │
│ │ 符号化依赖: │ │
│ │ • dSYM 文件(包含调试信息) │ │
│ │ • 编译时的 DWARF 调试信息 │ │
│ │ • 运行时的符号表 │ │
│ └────────────────────────────────────────────────────────────────┘ │
└──────────────────────────────────────────────────────────────────────┘9.2 Time Profiler 火焰图解读
火焰图(Flame Graph)解读方法:
┌──────────────────────────────────────────────────────────────────────┐
│ 火焰图结构说明: │
│ │
│ ┌────────────────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ 火焰图解读规则: │ │
│ │ │ │
│ │ • 横轴宽度 = 采样次数(越宽越耗时) │ │
│ │ • 纵轴深度 = 调用栈深度(从 main() 开始) │ │
│ │ • 每行一个函数 │ │
│ │ • 每个块是函数的调用片段 │ │
│ │ • 父块包含子块(从下往上调用) │ │
│ │ │ │
│ │ 火焰图示例: │ │
│ │ │ │
│ │ ┌─────────────────────────────────────────────────────┐ │ │
│ │ │ [main] │ │ │
│ │ │ ┌─────────────────────┐ ┌─────────────┐ │ │ │
│ │ │ │ [UIApplicationMain] │ │ [RunLoop] │ │ │ │
│ │ │ ┌───┴──────────────────┐ │ └─────────────┘ │ │ │
│ │ │ │ [ViewController] │ │ │ │ │
│ │ │ │ ┌──────────────┐ │ │ │ │ │
│ │ │ │ │ [tableView] │ │ │ │ │ │
│ │ │ │ │ ┌────────┐ │ │ │ │ │ │
│ │ │ │ │ │cellFor│ │ │ │ │ │ │
│ │ │ │ │ │RowAt] │ │ │ │ │ │
│ │ │ │ │ │ ┌───┐ │ │ │ │ │ │
│ │ │ │ │ │ │... │ │ │ │ │ │ │
│ │ │ │ │ │ └───┘ │ │ │ │ │ │
│ │ │ │ │ └────────┘ │ │ │ │ │ │
│ │ │ │ └──────────────┘ │ │ │ │ │
│ │ │ └─────────────────────┘ │ │ │ │
│ │ │ │ │ │ │
│ │ │ 横轴宽度表示:采样次数 → 耗时 │ │
│ │ └─────────────────────────────────────────────────────┘ │ │
│ │ │ │
│ │ 分析重点: │ │
│ │ ┌─────────────────────────────────────────────────────┐ │ │
│ │ │ 1. 最宽的柱子 → 耗时最长的函数(优先优化) │ │ │
│ │ │ 2. 最左边的分支 → 调用入口点 │ │ │
│ │ │ 3. 系统框架占比 → 占比过大需排查(如 UIKit 内部) │ │ │
│ │ │ 4. 递归调用 → 检查是否有无限递归 │ │ │
│ │ │ 5. 不在自己代码中的热点 → 可能是性能瓶颈 │ │ │
│ │ │ 6. 主线程 > 16ms → 卡顿(60fps 要求每帧 ≤ 16.67ms) │ │ │
│ │ └─────────────────────────────────────────────────────┘ │ │
│ └────────────────────────────────────────────────────────────────┘ │
│ │
│ 常见优化场景: │
│ ┌────────────────────────────────────────────────────────────────┐ │
│ │ 场景 │ 火焰图特征 │ 优化方案 │ │
│ ├───────────────┼─────────────────────────────┼──────────────────┤ │
│ │ 列表渲染卡顿 │ cellForRow 最宽 │ 预计算/缓存 │ │
│ │ JSON 解析慢 │ JSONSerialization 最宽 │ 使用 Swift JSON │ │
│ │ 图片加载慢 │ UIImage/CGImage 最宽 │ 异步加载/缓存 │ │
│ │ AutoLayout 慢 │ layoutSubviews 最宽 │ 减少约束/预布局 │ │
│ │ 字符串拼接慢 │ stringByAppending... 最宽 │ 使用 StringBuilder │ │
│ │ 网络请求同步 │ URLSession 在主线程 │ 异步 + Queue 切换 │ │
│ └───────────────┴─────────────────────────────┴──────────────────┘ │
└──────────────────────────────────────────────────────────────────────┘9.3 Time Profiler 最佳实践
Time Profiler 最佳实践:
┌──────────────────────────────────────────────────────────────────────┐
│ 采样设置建议: │
│ ┌────────────────────────────────────────────────────────────────┐ │
│ │ • 采样率:默认 1000Hz(一般场景足够) │ │
│ │ • 设备:优先 USB 真机(精度更高) │ │
│ │ • 开启 "Profile GPU":同时分析 GPU 性能 │ │
│ │ • 关闭 "Instruments":减少额外开销 │ │
│ │ • 预热运行:先跑几轮再开始记录 │ │
│ │ • 排除系统调用:勾选 "Profile GPU" 和 "Profile System Trace" │ │
│ │ • 对比分析:优化后重新采样对比 │ │
│ └────────────────────────────────────────────────────────────────┘ │
│ │
│ 常见卡顿分析场景: │
│ ┌────────────────────────────────────────────────────────────────┐ │
│ │ 场景 1:列表滚动卡顿 │ │
│ │ 方法:Time Profiler + Core Animation 组合 │ │
│ │ 定位:cellForRowAt 耗时 > 16ms → 优化 cell 配置 │ │
│ │ │ │
│ │ 场景 2:页面切换卡顿 │ │
│ │ 方法:Time Profiler + 查看 viewDidLoad 耗时 │ │
│ │ 定位:大量计算在 viewDidLoad → 延迟加载 │ │
│ │ │ │
│ │ 场景 3:动画掉帧 │ │
│ │ 方法:Core Animation + Time Profiler │ │
│ │ 定位:离屏渲染 / 复杂 CALayer 动画 │ │
│ │ │ │
│ │ 场景 4:启动慢 │ │
│ │ 方法:Time Profiler + 记录启动全过程 │ │
│ │ 定位:-application:didFinishLaunching: 中的耗时操作 │ │
│ └────────────────────────────────────────────────────────────────┘ │
└──────────────────────────────────────────────────────────────────────┘10. Allocations 内存分配追踪
10.1 Allocations 原理
Allocations 工具原理深度分析:
┌──────────────────────────────────────────────────────────────────────┐
│ Allocations 核心机制: │
│ │
│ ┌────────────────────────────────────────────────────────────────┐ │
│ │ 实现原理: │ │
│ │ │ │
│ │ 1. hooks malloc/free/calloc/realloc/new/delete │ │
│ │ 2. 每次分配记录: │ │
│ │ • 分配地址 │ │
│ │ • 分配大小 │ │
│ │ • 分配类型(NSObject / NSData / NSArray / ...) │ │
│ │ • 调用栈(完整栈帧) │ │
│ │ • 时间戳 │ │
│ │ • 线程 ID │ │
│ │ │ │
│ 3. 释放时也记录(用于计算 Live Bytes) │ │
│ 4. 通过快照(Snapshot)机制对比内存变化 │ │
│ │ │
│ 内存分配记录结构: │ │
│ ┌─────────────────────────────────────────────────────┐ │ │
│ │ struct AllocationRecord { │ │ │
│ │ void* address; // 分配地址 │ │ │
│ │ size_t size; // 分配大小 │ │ │
│ │ const char* type; // 分配类型名 │ │ │
│ │ u_int32_t count; // 计数 │ │ │
│ │ void* stack[64]; // 调用栈 │ │ │
│ │ u_int32_t stackDepth; // 栈深度 │ │ │
│ │ thread_t thread; // 分配线程 │ │ │
│ │ double timestamp; // 时间戳 │ │ │
│ │ }; │ │ │
│ └─────────────────────────────────────────────────────┘ │ │
│ └────────────────────────────────────────────────────────────────┘ │
│ │
│ Allocations 核心指标: │
│ ┌────────────────────────────────────────────────────────────────┐ │
│ │ 指标 │ 含义 │ 分析要点 │ │
│ ├────────────────────────────────────────────────────────────────┤ │
│ │ Live Bytes │ 当前活跃的内存总量 │ 持续增长=潜在 │ │
│ │ │ │ 泄漏 │ │
│ │ Allocs │ 对象分配次数 │ 高频分配=性能 │ │
│ │ │ │ 瓶颈 │ │
│ │ Bytes │ 总分配字节数 │ 分配总量 │ │
│ │ Refs │ 引用计数 │ 循环引用检测 │ │
│ │ Retained Size │ 对象及其引用链的总大小 │ 泄漏源头 │ │
│ │ Inactive Size │ 非活跃内存(可能即将释放) │ GC 评估 │ │
│ │ Generation │ 内存代数(分代追踪) │ 短期vs长期对象 │ │
│ └────────────────────────────────────────────────────────────────┘ │
└──────────────────────────────────────────────────────────────────────┘10.2 Allocations 使用指南
Allocations 使用指南:
┌──────────────────────────────────────────────────────────────────────┐
│ 使用步骤: │
│ │
│ 1. Open Instruments → Allocations │
│ 2. 选择设备/模拟器 │
│ 3. 点击 Record │
│ 4. 执行测试场景 │
│ 5. 观察 Live Bytes 曲线 │
│ 6. 使用 Snapshot 对比内存变化 │
│ 7. 定位问题代码 │
│ │
│ Snapshot 对比工作流程: │
│ ┌────────────────────────────────────────────────────────────────┐ │
│ │ Step 1: 点击 "+" 按钮 → 创建 Snapshot A (基线) │ │
│ │ ↓ │ │
│ │ Step 2: 执行操作(如滑动列表 100 次) │ │
│ │ ↓ │ │
│ │ Step 3: 点击 "+" 按钮 → 创建 Snapshot B │ │
│ │ ↓ │ │
│ │ Step 4: 查看 A → B 的差异(Byte Increase) │ │
│ │ ↓ │ │
│ │ Step 5: 按 Byte Increase 排序 → 找到增长最多的对象类型 │ │
│ │ ↓ │ │
│ │ Step 6: 双击查看该类型的分配调用栈 │ │
│ │ ↓ │ │
│ │ Step 7: 定位到代码行 → 修复 │ │
│ └────────────────────────────────────────────────────────────────┘ │
│ │
│ 常见内存增长模式: │
│ ┌────────────────────────────────────────────────────────────────┐ │
│ │ 模式 │ 特征 │ 原因 │ │
│ ├───────────────┼─────────────────────────────┼────────────────┤ │
│ │ 线性增长 │ Live Bytes 持续上升 │ 对象未释放 │ │
│ │ 阶梯增长 │ 增长→稳定→增长→稳定 │ 缓存未清理 │ │
│ │ 脉冲增长 │ 偶尔突增→恢复 │ 大对象分配 │ │
│ │ 周期性增长 │ 周期性上升 │ 定时器/轮询 │ │
│ │ 视图控制器未释放 │ VC 实例数量持续增长 │ 循环引用 │ │
│ │ 数组无限增长 │ [AnyObject] 的 Count 持续增长 │ 缓存无上限 │ │
│ └───────────────┴─────────────────────────────┴────────────────┘ │
│ │
│ 分配类型分析技巧: │
│ ┌────────────────────────────────────────────────────────────────┐ │
│ │ • 右键列 → 选择 "Only Show Heap" 排除系统分配 │ │
│ │ • 右键列 → "Filter" → 输入类名过滤 │ │
│ │ • 右键列 → "Sort By" → 按 Allocs/Bytes/Refs 排序 │ │
│ │ • 使用 "Generate Heap Snapshot" 对比时间点 │ │
│ │ • 检查 "Retain Cycle" 检测(自动识别循环引用) │ │
│ └────────────────────────────────────────────────────────────────┘ │
└──────────────────────────────────────────────────────────────────────┘10.3 内存泄漏检测实战
内存泄漏检测实战:
┌──────────────────────────────────────────────────────────────────────┐
│ 内存泄漏检测流程图: │
│ │
│ ┌────────────────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ 1. 使用 Allocations 检测内存增长 │ │
│ │ ↓ │ │
│ │ 2. 使用 Leaks 确认是否存在泄漏 │ │
│ │ ↓ │ │
│ │ 3. 使用 Zombies 确认是否野指针 │ │
│ │ ↓ │ │
│ │ 4. 定位泄漏代码 │ │
│ │ ↓ │ │
│ │ 5. 修复泄漏 │ │
│ │ ↓ │ │
│ │ 6. 验证修复(再次 Profile 对比) │ │
│ │ │ │
│ 常见泄漏原因及修复方案: │ │
│ ┌──────────────┬──────────────────────────┬───────────────────┐ │
│ │ 泄漏原因 │ 检测方式 │ 修复方案 │ │
│ ├──────────────┼──────────────────────────┼───────────────────┤ │
│ │ 闭包循环引用 │ Allocations + Retain │ [weak self]/ │ │
│ │ │ Cycle 检测 │ [unowned self] │ │
│ │ NSTimer │ Leaks │ timer.invalidate() │ │
│ │ NotificationCenter│ Allocations/Leaks │ removeObserver │ │
│ │ KVO │ Allocations/Leaks │ removeObserver │ │
│ │ CADisplayLink │ Leaks │ invalidate() │ │
│ │ Delegate │ Retain Cycle 检测 │ weak delegate │ │
│ │ 缓存无上限 │ Allocations 持续增长 │ 设置缓存上限 │ │
│ │ 通知/回调 │ Allocations │ 清理注册 │ │
│ │ 定时器未取消 │ Leaks │ 统一清理机制 │ │
│ └──────────────┴──────────────────────────┴───────────────────┘ │
│ │
│ Swift 循环引用检测示例: │
│ ┌────────────────────────────────────────────────────────────────┐ │
│ │ // ❌ 泄漏 - 闭包循环引用 │ │
│ │ class MyViewController: UIViewController { │ │
│ │ override func viewDidLoad() { │ │
│ │ super.viewDidLoad() │ │
│ │ Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { │ │
│ │ self.doSomething() // 💥 self 被闭包捕获 │ │
│ │ } │ │
│ │ } │ │
│ │ } │ │
│ │ │ │
│ │ // ✅ 修复 - weak self │ │
│ │ class MyViewController: UIViewController { │ │
│ │ override func viewDidLoad() { │ │
│ │ super.viewDidLoad() │ │
│ │ timer = Timer.scheduledTimer(withTimeInterval: 1.0, │ │
│ │ repeats: true) { [weak self] _ in │ │
│ │ self?.doSomething() // ✅ 安全 │ │
│ │ } │ │
│ │ } │ │
│ │ } │ │
│ └────────────────────────────────────────────────────────────────┘ │
└──────────────────────────────────────────────────────────────────────┘11. Leaks 内存泄漏检测
11.1 Leaks 原理
Leaks 检测原理深度分析:
┌──────────────────────────────────────────────────────────────────────┐
│ Leaks 工具原理: │
│ │
│ ┌────────────────────────────────────────────────────────────────┐ │
│ │ 内存泄漏检测算法(标记-清除变体): │ │
│ │ │ │
│ │ 1. 分配标记阶段(Allocation Marking) │ │
│ │ • Instruments hooks malloc/free │ │
│ │ • 所有已分配内存被标记为 "已分配" │ │
│ │ │ │
│ │ 2. 根对象遍历阶段(Root Traversal) │ │
│ │ • 从根对象开始(全局变量、栈变量、寄存器) │ │
│ │ • 遍历所有引用链(retain/release 跟踪) │ │
│ │ • 所有可达对象被标记为 "可达" │ │
│ │ │ │
│ │ 3. 清除阶段(Sweeping) │ │
│ │ • 已分配但未可达的对象 = 泄漏 │ │
│ │ • 记录泄漏地址、大小、类型、调用栈 │ │
│ │ │ │
│ │ 泄漏检测精度: │ │
│ │ ┌───────────────┬──────────────────────────────────────────┐ │ │
│ │ │ 检测能力 │ 精度 │ │ │
│ │ ├───────────────┼──────────────────────────────────────────┤ │ │
│ │ │ 完全泄漏 │ ✅ 100%(对象完全不可达) │ │ │
│ │ │ 部分泄漏 │ ✅ 90%+(至少部分不可达) │ │ │
│ │ │ 暂存泄漏 │ ❌ 可能误报(对象暂时无引用) │ │ │
│ │ │ 延迟释放 │ ⚠️ 可能误报(对象将在下次循环释放) │ │ │
│ │ │ ARC 对象 │ ✅ ARC 精确追踪 │ │ │
│ │ │ CF 对象 │ ✅ Retain/Release 追踪 │ │ │
│ │ │ malloc 内存 │ ✅ malloc/free 追踪 │ │ │
│ │ └───────────────┴──────────────────────────────────────────┘ │ │
│ └────────────────────────────────────────────────────────────────┘ │
│ │
│ Leaks 工作流程: │
│ ┌────────────────────────────────────────────────────────────────┐ │
│ │ 1. 启动 Leaks 工具 │ │
│ │ 2. 记录操作 │ │
│ │ 3. Instruments 自动检测泄漏(每 1 秒一次) │ │
│ │ 4. 双击泄漏对象 → 查看调用栈 │ │
│ │ 5. 定位泄漏代码 │ │
│ │ 6. 修复后重新验证 │ │
│ └────────────────────────────────────────────────────────────────┘ │
│ │
│ Leaks 检测 vs ARC 自动内存管理: │
│ ┌────────────────────────────────────────────────────────────────┐ │
│ │ 重要理解: │ │
│ │ • ARC 不等于没有内存泄漏! │ │
│ │ • ARC 防止的是"过度释放",不防止"循环引用" │ │
│ │ • 循环引用 = 互相强引用 → 永远无法 dealloc │ │
│ │ • ARC 不会自动检测循环引用 │ │
│ │ • Leaks 工具检测的就是这种 ARC 无法解决的问题 │ │
│ └────────────────────────────────────────────────────────────────┘ │
└──────────────────────────────────────────────────────────────────────┘11.2 常见泄漏场景详解
内存泄漏场景全解(含修复代码):
┌──────────────────────────────────────────────────────────────────────┐
│ 场景 1:闭包循环引用(最常见) │
│ ┌────────────────────────────────────────────────────────────────┐ │
│ │ // ❌ 泄漏:闭包捕获 self │ │
│ │ class MyViewController: UIViewController { │ │
│ │ var completion: (() -> Void)? │ │
│ │ func fetchData() { │ │
│ │ network.fetch { [self] data in // 💥 循环引用! │ │
│ │ self.updateUI(data) │ │
│ │ } │ │
│ │ } │ │
│ │ } │ │
│ │ │ │
│ │ // ✅ 修复:weak + unowned │ │
│ │ func fetchData() { │ │
│ │ network.fetch { [weak self] data in // ✅ 强引用周期被打破 │ │
│ │ self?.updateUI(data) // self 可能为 nil,安全 │ │
│ │ } │ │
│ │ } │ │
│ │ │ │
│ │ // ✅ 更安全的写法:guard weak │ │
│ │ func fetchData() { │ │
│ │ network.fetch { [weak self] data in // ✅ │ │
│ │ guard let self = self else { return } │ │
│ │ self.updateUI(data) // self 是确定的 │ │
│ │ } │ │
│ │ } │ │
│ └────────────────────────────────────────────────────────────────┘ │
│ │
│ 场景 2:NSTimer 未 invalidate │
│ ┌────────────────────────────────────────────────────────────────┐ │
│ │ // ❌ 泄漏:Timer 强引用 self │ │
│ │ timer = Timer.scheduledTimer( │ │
│ │ withTimeInterval: 1.0, │ │
│ │ repeats: true, │ │
│ │ target: self, // 💥 self 被 Timer 强引用 │ │
│ │ selector: #selector(update), │ │
│ │ userInfo: nil, │ │
│ │ repeats: true │ │
│ │ ) │ │
│ │ │ │
│ │ // ✅ 修复:使用 RunLoop + Block Timer │ │
│ │ timer = Timer.scheduledTimer(withTimeInterval: 1.0, │ │
│ │ repeats: true) { [weak self] _ in // ✅ │ │
│ │ self?.update() │ │
│ │ } │ │
│ │ │ │
│ │ // ⚠️ 注意:viewWillDisappear 中仍需 invalidate │ │
│ │ override func viewWillDisappear(_ animated: Bool) { │ │
│ │ super.viewWillDisappear(animated) │ │
│ │ timer?.invalidate() │ │
│ │ timer = nil │ │
│ │ } │ │
│ └────────────────────────────────────────────────────────────────┘ │
└──────────────────────────────────────────────────────────────────────┘11.3 更多泄漏场景
更多内存泄漏场景(续):
┌──────────────────────────────────────────────────────────────────────┐
│ 场景 3:NotificationCenter 未移除 Observer │
│ ┌────────────────────────────────────────────────────────────────┐ │
│ │ // ❌ 泄漏:移除 Observer 的时机不对 │ │
│ │ override func viewDidLoad() { │ │
│ │ super.viewDidLoad() │ │
│ │ NotificationCenter.default.addObserver( │ │
│ │ self, │ │
│ │ selector: #selector(didReceive), │ │
│ │ name: .MyEvent, │ │
│ │ object: nil │ │
│ │ ) │ │
│ │ } │ │
│ │ │ │
│ │ // ⚠️ 常见问题: │ │
│ │ • 在 dealloc 中移除 → ViewController 可能已经释放 │ │
│ │ • 忘记移除 → 永久泄漏 │ │
│ │ │ │
│ │ // ✅ 修复:在合适的时机移除 │ │
│ │ override func viewWillDisappear(_ animated: Bool) { │ │
│ │ super.viewWillDisappear(animated) │ │
│ │ NotificationCenter.default.removeObserver(self) │ │
│ │ } │ │
│ │ │ │
│ │ // ✅ 更安全的写法: │ │
│ │ var notificationObserver: NSObjectProtocol? │ │
│ │ override func viewDidLoad() { │ │
│ │ notificationObserver = NotificationCenter.default │ │
│ │ .addObserver(forName: .MyEvent, │ │
│ │ object: nil, │ │
│ │ queue: .main) { [weak self] _ in │ │
│ │ self?.didReceive() │ │
│ │ } │ │
│ │ } │ │
│ │ deinit { │ │
│ │ if let observer = notificationObserver { │ │
│ │ NotificationCenter.default.removeObserver(observer) │ │
│ │ } │ │
│ │ } │ │
│ └────────────────────────────────────────────────────────────────┘ │
│ │
│ 场景 4:KVO 未 removeObserver │
│ ┌────────────────────────────────────────────────────────────────┐ │
│ │ // ❌ 泄漏:KVO Observer 未移除 │ │
│ │ override func viewDidLoad() { │ │
│ │ observedObject.addObserver( │ │
│ │ self, │ │
│ │ forKeyPath: "value", │ │
│ │ options: [.new], │ │
│ │ context: nil │ │
│ │ ) │ │
│ │ } │ │
│ │ │ │
│ │ // ✅ 修复:确保配对使用 │ │
│ │ override func dealloc() { │ │
│ │ observedObject.removeObserver( │ │
│ │ self, │ │
│ │ forKeyPath: "value" │ │
│ │ ) │ │
│ │ } │ │
│ │ │ │
│ │ // ⚠️ 最佳实践:使用 KeyPath + 类型安全 API │ │
│ │ @objc dynamic var value: Int = 0 │ │
│ │ observe(\.value, options: [.new]) { [weak self] obj, change in │ │
│ │ self?.handleValueChange(change.newValue) │ │
│ │ } │ │
│ └────────────────────────────────────────────────────────────────┘ │
│ │
│ 场景 5:CADisplayLink / NSURLConnection 未清理 │
│ ┌────────────────────────────────────────────────────────────────┐ │
│ │ // ❌ 泄漏:CADisplayLink 未 invalidate │ │
│ │ var displayLink: CADisplayLink? │ │
│ │ override func viewDidLoad() { │ │
│ │ displayLink = CADisplayLink(target: self, │ │
│ │ selector: #selector(updateFrame)) │ │
│ │ displayLink?.add(to: .main, forMode: .default) │ │
│ │ } │ │
│ │ │ │
│ │ // ✅ 修复:在适当时机 invalidate │ │
│ │ override func viewWillDisappear(_ animated: Bool) { │ │
│ │ super.viewWillDisappear(animated) │ │
│ │ displayLink?.invalidate() │ │
│ │ displayLink = nil │ │
│ │ } │ │
│ │ │ │
│ │ // ✅ 更安全的写法: │ │
│ │ // 使用 weak self + guard 模式 │ │
│ │ displayLink = CADisplayLink(target: self, │ │
│ │ selector: #selector(updateFrame)) │ │
│ │ displayLink?.add(to: .main, forMode: .default) │ │
│ │ deinit { │ │
│ │ displayLink?.invalidate() // ✅ 在 dealloc 中清理 │ │
│ │ } │ │
│ └────────────────────────────────────────────────────────────────┘ │
└──────────────────────────────────────────────────────────────────────┘11.4 泄漏检测工具对比
内存泄漏检测工具对比:
┌──────────────────────────────────────────────────────────────────────┐
│ 工具 │ 检测能力 │ 精度 │ 性能开销 │ 适用阶段 │
├──────────────────────────────────────────────────────────────────────┤
│ Leaks │ 完全/部分泄漏 │ 高 │ 低 │ 测试阶段 │
│ Allocations │ 内存增长+泄漏 │ 中高 │ 中 │ 开发阶段 │
│ ASan │ 使用后释放 │ 高 │ 高(2x-8x)│ 测试阶段 │
│ LeakCanary │ Android 专用 │ 高 │ 低 │ Android │
│ Xcode 内存图 │ 可视化内存布局 │ 中 │ 低 │ 调试阶段 │
│ AddressBook │ 内存泄漏(实验性) │ 高 │ 高 │ 测试阶段 │
│ 自定义 retain │ 引用计数监控 │ 中 │ 低 │ 开发阶段 │
│ instrumentation │ 内存钩子 │ 高 │ 中 │ 生产分析 │
└──────────────────────────────────────────────────────────────────────┘12. Zombies 悬垂指针检测
12.1 Zombies 原理
Zombies 工具原理:
┌──────────────────────────────────────────────────────────────────────┐
│ Zombies 工作原理: │
│ │
│ ┌────────────────────────────────────────────────────────────────┐ │
│ │ 工作原理: │ │
│ │ │ │
│ │ 正常对象生命周期: │ │
│ │ 创建 → retain → use → release → dealloc → 内存回收 │ │
│ │ │ │
│ │ Zombies 模式: │ │
│ │ 创建 → retain → use → release → "zombie"(不死,改名) │ │
│ │ │ │
│ │ 当 zombie 对象收到消息时: │ │
│ │ ┌───────────────────────────────────────────────────────┐ │ │
│ │ │ 1. 拦截 -retain/-release/-autorelease │ │ │
│ │ │ 2. release 时不 dealloc,改为: │ │ │
│ │ │ • 设置 isa 指向 _NSZombie_<ClassName> │ │ │
│ │ │ • 保存原类名和原始地址 │ │ │
│ │ │ • 将对象"复活"为僵尸对象 │ │ │
│ │ │ 3. 当 zombie 对象收到消息时: │ │ │
│ │ │ • 捕获 -forwardInvocation: 或 -methodSignatureForSelector: │
│ │ │ • 打印崩溃信息: │ │ │
│ │ │ "Message sent to deallocated instance" │ │ │
│ │ │ 原对象类名、地址、创建/释放调用栈 │ │ │
│ │ └───────────────────────────────────────────────────────┘ │ │
│ └────────────────────────────────────────────────────────────────┘ │
│ │
│ Zombies 与 ASan 对比: │
│ ┌────────────────────────────────────────────────────────────────┐ │
│ │ 特性 │ Zombies │ ASan │ │
│ ├────────────────────────────────────────────────────────────────┤ │
│ │ 检测内容 │ OC 对象使用后释放 │ 所有内存使用后释放 │ │
│ │ 适用语言 │ Objective-C │ C/C++/Swift/OC │ │
│ │ 性能开销 │ 低 │ 高 (2x-8x) │ │
│ │ 精确度 │ 中等(可能漏检) │ 高 │ │
│ │ 崩溃信息 │ 消息名 + 类名 + 地址 │ 精确的行号和调用栈 │ │
│ │ 生产可用 │ 否 │ 否 │ │
│ │ 检测时机 │ 运行时 │ 运行时 │ │
│ │ 设置方式 │ NSZombieEnabled=YES │ Xcode Scheme → ASan │ │
│ └────────────────────────────────────────────────────────────────┘ │
│ │
│ 启用 Zombies: │
│ ┌────────────────────────────────────────────────────────────────┐ │
│ │ 方法 1: Xcode Scheme │ │
│ │ Xcode → Product → Scheme → Diagnostics → Enable Zombies │ │
│ │ │ │
│ │ 方法 2: 环境变量 │ │
│ │ export NSZombieEnabled=YES │ │
│ │ export NSAutoreleaseFreedObjectWriteTracking=YES │ │
│ │ │ │
│ │ 方法 3: launchctl │ │
│ │ launchctl setenv NSZombieEnabled YES │ │
│ └────────────────────────────────────────────────────────────────┘ │
│ │
│ Zombies 输出信息解读: │
│ ┌────────────────────────────────────────────────────────────────┐ │
│ │ 典型输出: │ │
│ │ "*** -[MyClass doSomething]: message sent to deallocated │ │
│ │ instance 0x600003fa0000" │ │
│ │ │ │
│ │ 解读: │ │
│ │ • MyClass 的实例 0x600003fa0000 已经被释放 │ │
│ │ • 有人试图调用 doSomething 方法 │ │
│ │ • 修复:检查谁持有了这个对象的引用 │ │
│ │ • 检查 delegate 是否置 nil │ │
│ │ • 检查闭包是否捕获了 self │ │
│ └────────────────────────────────────────────────────────────────┘ │
└──────────────────────────────────────────────────────────────────────┘13. Core Animation 渲染分析
13.1 Core Animation 架构
Core Animation 渲染机制:
┌──────────────────────────────────────────────────────────────────────┐
│ Core Animation 渲染管线: │
│ │
│ ┌────────────────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ 渲染管线: │ │
│ │ │ │
│ │ Layer Tree (CPU) → Render Server (GPU) │ │
│ │ ┌───────────────────┐ ┌──────────────────┐ │ │
│ │ │ UIView hierarchy │ commit │ CA::Transaction │ │ │
│ │ │ (逻辑树) │ ──────────→ │ (渲染事务) │ │ │
│ │ │ │ │ │ │ │
│ │ │ • layout │ render pass │ • 几何变换 │ │ │
│ │ │ • content │ ──────────→ │ • 色彩管理 │ │ │
│ │ │ • 变换 │ │ • 合成 (Compositing) │ │ │
│ │ │ • 阴影/圆角 │ │ • 栅格化 │ │ │
│ │ │ • 动画 │ │ • 优化 (Optimizing) │ │ │
│ │ └───────────────────┘ └──────────────────┘ │ │
│ │ │ │
│ │ 关键概念: │ │
│ │ • Commit:layerTree → RenderServer │ │
│ │ • Render Pass:将 layer 信息发给 GPU │ │
│ │ • Tiling:将屏幕分片(tiling) │ │
│ │ • Rasterization:光栅化(栅格化) │ │
│ │ • Display:显示到屏幕 │ │
│ │ │ │
│ │ 渲染时序(60fps = 16.67ms/帧): │ │
│ │ ┌──────────────────────────────────────────────────────┐ │ │
│ │ │ Layout (计算约束) → Render → Commit → Display → VSync │ │ │
│ │ │ 0ms ────────────── 8ms ───── 10ms ──── 14ms ───── 16.67ms │ │ │
│ │ │ │ │ │
│ │ │ 关键:每帧必须在 16.67ms 内完成 │ │ │
│ │ └──────────────────────────────────────────────────────┘ │ │
│ └────────────────────────────────────────────────────────────────┘ │
│ │
│ Core Animation 常见性能问题: │
│ ┌────────────────────────────────────────────────────────────────┐ │
│ │ 问题 │ 原因 │ 解决方案 │ │
│ ├─────────────────┼─────────────────────────┼──────────────────┤ │
│ │ 离屏渲染 │ 圆角/shadow/clip │ mipmap/自定义绘制 │ │
│ │ 过度栅格化 │ shouldRasterize=YES │ 栅格化后缓存 │ │
│ │ 频繁重绘 │ contents 频繁变更 │ 缓存 contents │ │
│ │ 复杂阴影 │ shadowPath 不设置 │ 设置 shadowPath │ │
│ │ 大圆角 │ cornerRadius 大值 │ maskedCorners/ │ │
│ │ │ │ UIBezierPath │ │
│ │ 不必要的动画 │ implicit animation │ removeAllAnimations│ │
│ │ 图层层级过深 │ 嵌套 layer > 5 层 │ 扁平化 layer 树 │ │
│ │ 透明度过多 │ alpha < 1.0 │ 减少透明图层 │ │
│ └─────────────────┴─────────────────────────┴──────────────────┘ │
└──────────────────────────────────────────────────────────────────────┘13.2 Core Animation 工具使用
Core Animation Instruments 使用:
┌──────────────────────────────────────────────────────────────────────┐
│ Core Animation 工具分析内容: │
│ │
│ ┌────────────────────────────────────────────────────────────────┐ │
│ │ 指标 │ 含义 │ 阈值 │ │
│ ├─────────────────┼─────────────────────────┼──────────────────┤ │
│ │ Tile Scale │ 瓦片缩放比 │ ≤ 1.0(理想) │ │
│ │ Offscreen Draws │ 离屏渲染次数 │ 0(理想) │ │
│ │ Color Blended │ 颜色混合次数 │ 越少越好 │ │
│ │ Shaded Rects │ 阴影区域 │ 越少越好 │ │
│ │ Pixel Fill │ 像素填充率 │ ≤ 1.0 │ │
│ │ Texture Upload │ GPU 纹理上传 │ 越少越好 │ │
│ │ Texture Alloc │ GPU 纹理分配 │ 0(理想) │ │
│ │ Texture Resize │ GPU 纹理重设 │ 越少越好 │ │
│ │ Core Image │ CI 滤镜使用 │ 越少越好 │ │
│ │ Metal │ Metal 调用 │ 越少越好 │ │
│ │ Swap Buffers │ 缓冲区交换次数 │ = 帧数 │ │
│ │ Draw Calls │ GPU 绘制调用 │ 越少越好 │ │
│ │ Layer Copies │ 图层拷贝次数 │ 0(理想) │ │
│ │ GPU Time │ GPU 耗时 │ < 10ms/帧 │ │
│ │ Tile Time │ 瓦片时间 │ < 8ms/帧 │ │
│ └─────────────────┴─────────────────────────┴──────────────────┘ │
│ │
│ Core Animation 最佳实践: │
│ ┌────────────────────────────────────────────────────────────────┐ │
│ │ • 避免离屏渲染:使用 CAShapeLayer + bezierPath │ │
│ │ • 减少阴影:使用预渲染的 shadowImage │ │
│ │ • 缓存渲染结果:cachedContext / shouldRasterize = YES(谨慎) │ │
│ │ • 使用 CoreGraphics 自定义绘制(比系统视图更快) │ │
│ │ • 减少透明图层:alpha < 1.0 触发混合 │ │
│ │ • 预计算布局:缓存 layoutSubviews 结果 │ │
│ │ • 使用 CADisplayLink 替代 Timer 做动画 │ │
│ │ • 使用 CATransaction 批量更新(减少 commit 次数) │ │
│ │ • 图片尺寸匹配屏幕(避免系统缩放) │ │
│ │ • 使用 UIGraphicsImageRenderer 替代 UIGraphicsBeginImageContext │ │
│ └────────────────────────────────────────────────────────────────┘ │
└──────────────────────────────────────────────────────────────────────┘14. Network & Energy 工具
14.1 Network 工具
Network Instruments 工具:
┌──────────────────────────────────────────────────────────────────────┐
│ Network 工具分析内容: │
│ │
│ ┌────────────────────────────────────────────────────────────────┐ │
│ │ 指标 │ 含义 │ 关注点 │ │
│ ├───────────────────┼─────────────────────────┼─────────────────┤ │
│ │ Duration │ 请求持续时间 │ > 1s 需优化 │ │
│ │ Data Sent │ 发送数据量 │ 减少 payload │ │
│ │ Data Received │ 接收数据量 │ 压缩/分页 │ │
│ │ DNS Lookup │ DNS 解析时间 │ CDN/本地缓存 │ │
│ │ TCP Connect │ TCP 连接建立时间 │ 连接复用 │ │
│ │ TLS Handshake │ TLS 握手时间 │ TLS 会话复用 │ │
│ │ Time to First Byte│ 首字节时间(TTFB) │ 后端优化 │ │
│ │ Total Time │ 总耗时 │ < 200ms(理想) │ │
│ │ Retry Count │ 重试次数 │ 重试策略 │ │
│ │ Error Count │ 错误次数 │ 错误处理 │ │
│ │ Cache Status │ 缓存状态 │ 缓存命中率 │ │
│ │ Protocol │ 协议版本 │ HTTP/2 vs HTTP/1 │ │
│ │ IP Address │ 服务器地址 │ 就近访问 │ │
│ │ SSL Certificate │ SSL 证书信息 │ 证书有效性 │ │
│ └───────────────────┴─────────────────────────┴─────────────────┘ │
│ │
│ Network 工具使用技巧: │
│ ┌────────────────────────────────────────────────────────────────┐ │
│ │ • 使用 "Network Link Conditioner" 模拟弱网 │ │
│ │ • 使用 "Slow Motion Network" 慢速网络模拟 │ │
│ │ • 关注重定向链(减少重定向次数) │ │
│ │ • 检查 HTTP 缓存头(Cache-Control, ETag) │ │
│ │ • 使用 gzip/brotli 压缩 │ │
│ │ • 启用 HTTP/2 多路复用 │ │
│ │ • 连接池(Keep-Alive)减少握手开销 │ │
│ │ • 使用 CDN 加速静态资源 │ │
│ │ • 图片 WebP 格式 + 懒加载 │ │
│ └────────────────────────────────────────────────────────────────┘ │
└──────────────────────────────────────────────────────────────────────┘14.2 Energy Log 工具
Energy Log Instruments 工具:
┌──────────────────────────────────────────────────────────────────────┐
│ Energy Log 能耗分析: │
│ │
│ ┌────────────────────────────────────────────────────────────────┐ │
│ │ 能耗维度: │ │
│ │ │ │
│ │ CPU Energy:CPU 能耗 │ │
│ │ • 频率调整(P-State):频率越高能耗越大 │ │
│ │ • 核心数:使用更多核心 = 更高能耗 │ │
│ │ • 空闲 vs 活跃:空闲时能耗极低 │ │
│ │ │ │
│ │ GPU Energy:GPU 能耗 │ │
│ │ • 渲染复杂度:三角形数量/纹理大小 │ │
│ │ • 着色器复杂度:指令数 │ │
│ │ • 像素填充率:Overdraw 越多能耗越大 │ │
│ │ │ │
│ │ Network Energy:网络能耗 │ │
│ │ • 传输数据量:数据量越大能耗越大 │ │
│ │ • 信号强度:信号越弱能耗越大 │ │
│ │ • 协议:HTTP/2 比 HTTP/1 能耗更低 │ │
│ │ │ │
│ │ Wi-Fi / Bluetooth Energy: │ │
│ │ • 连接状态:连接中 vs 空闲 │ │
│ │ • 数据传输频率 │ │
│ │ │ │
│ │ Disk Energy:磁盘能耗 │ │
│ │ • 读写频率:频繁读写 = 高能耗 │ │
│ │ • 数据量 │ │
│ └────────────────────────────────────────────────────────────────┘ │
│ │
│ 能耗优化建议: │
│ ┌────────────────────────────────────────────────────────────────┐ │
│ │ • 减少后台任务频率 │ │
│ │ • 使用 Core Location 的 significantChange 代替频繁定位 │ │
│ │ • 减少网络请求频率 │ │
│ │ • 批量处理数据 │ │
│ │ • 使用 NSUrlConnection 的缓存 │ │
│ │ • 减少 CPU 密集型操作(压缩/加密/计算) │ │
│ │ • 使用 Metal 替代 CoreGraphics(GPU 加速) │ │
│ │ • 减少 AutoLayout 约束数量 │ │
│ │ • 使用 CADisplayLink 而非 Timer 做动画 │ │
│ │ • 减少透明图层(触发混合计算) │ │
│ └────────────────────────────────────────────────────────────────┘ │
└──────────────────────────────────────────────────────────────────────┘15. System Trace 系统级追踪
15.1 System Trace 原理
System Trace(系统级追踪)原理:
┌──────────────────────────────────────────────────────────────────────┐
│ System Trace 工具: │
│ │
│ ┌────────────────────────────────────────────────────────────────┐ │
│ │ 追踪范围: │ │
│ │ │ │
│ │ 内核层面: │ │
│ │ • 调度(Scheduling):进程/线程切换 │ │
│ │ • 内存管理:page fault / swap │ │
│ │ • I/O:磁盘/网络/蓝牙 I/O │ │
│ │ • 电源管理:电源状态变化 │ │
│ │ • Mach 端口:进程间通信 │ │
│ │ • 文件系统:VFS 调用 │ │
│ │ │ │
│ │ 用户态层面: │ │
│ │ • dyld 加载 │ │
│ │ • 框架调用(UIKit/Foundation 等) │ │
│ │ • 信号处理 │ │
│ │ • 系统调用 │ │
│ │ │ │
│ │ 使用场景: │ │
│ │ • 启动时间分析(launch time) │ │
│ │ • 死锁/饥饿分析 │ │
│ │ • I/O 性能分析 │ │
│ │ • 系统事件关联 │ │
│ │ • 框架性能分析 │ │
│ │ │ │
│ │ 与 Time Profiler 对比: │ │
│ │ ┌────────────────┬─────────────────┬──────────────────────┐ │ │
│ │ │ 维度 │ System Trace │ Time Profiler │ │ │
│ │ ├────────────────┼─────────────────┼──────────────────────┤ │ │
│ │ │ 范围 │ 全系统 │ 仅用户态(进程) │ │ │
│ │ │ 粒度 │ 事件级 │ 采样级 │ │ │
│ │ │ 开销 │ 高 │ 低 │ │ │
│ │ │ 记录时长 │ 短时(秒级) │ 长时(分钟级) │ │ │
│ │ │ 系统事件 │ ✅ │ ❌ │ │ │
│ │ │ 进程间通信 │ ✅ │ ❌ │ │ │
│ │ │ 内核调度 │ ✅ │ ❌ │ │ │
│ │ │ 文件 I/O │ ✅ │ ❌ │ │ │
│ │ │ 帧渲染 │ ✅ │ ⚠️ 间接 │ │ │
│ │ │ 使用复杂度 │ 高 │ 低 │ │ │
│ │ └────────────────┴─────────────────┴──────────────────────┘ │ │
│ └────────────────────────────────────────────────────────────────┘ │
│ │
│ System Trace 输出解读: │
│ ┌────────────────────────────────────────────────────────────────┐ │
│ │ 主要事件类型: │ │
│ │ • thread_switch → 线程切换 │ │
│ │ • mach_msg_send/receive → IPC 消息 │ │
│ │ • io_write/read → 磁盘 I/O │ │
│ │ • network_* → 网络事件 │ │
│ │ • vnodes_* → 文件系统事件 │ │
│ │ • com_apple_Mach_exception → 异常事件 │ │
│ │ • com_apple_mach_exc_server → Mach 异常服务器 │ │
│ │ • kern_task_suspend/resume → 进程挂起/恢复 │ │
│ │ • dispatch_async/async_work → GCD 事件 │ │
│ │ • CA::transaction → Core Animation 事务 │ │
│ └────────────────────────────────────────────────────────────────┘ │
└──────────────────────────────────────────────────────────────────────┘16. 模拟器调试
16.1 Simulator 调试功能
模拟器调试功能全景:
┌──────────────────────────────────────────────────────────────────────┐
│ 模拟器调试功能一览: │
│ │
│ ┌─────────────────────┬──────────────────────┬───────────────────┐ │
│ │ 功能 │ 操作方式 │ 调试场景 │ │
│ ├─────────────────────┼──────────────────────┼───────────────────┤ │
│ │ 旋转设备 │ ⌘ + ←/→ │ 横竖屏适配 │ │
│ │ 缩放 │ ⌘ + +/− │ 响应式布局 │ │
│ │ 模拟来电 │ Features → Simulate │ 电话功能测试 │ │
│ │ 模拟短信 │ Incoming Call │ SMS 功能测试 │ │
│ │ 模拟锁屏 │ ⌘ + ⌥ + E │ 锁屏/唤醒流程 │ │
│ │ 模拟 GPS 位置 │ Features → Location │ LBS 功能测试 │ │
│ │ 模拟摇晃撤销 │ ⌘ + ← │ 撤销功能测试 │ │
│ │ 慢速网络 │ Network Link │ 弱网测试 │ │
│ │ GPU Frame Capture │ ⌘ + G │ Metal 帧分析 │ │
│ │ GPU Driver Debug │ GPU Frame Capture │ Metal 驱动调试 │ │
│ │ 内存压力模拟 │ ⌘ + M │ 内存不足场景 │ │
│ │ 低电量模拟 │ Features → Battery │ 低电量处理 │ │
│ │ 多任务界面 │ ⌘ + Shift + H │ App 切换测试 │ │
│ │ 截屏 │ ⌘ + S │ UI 截图 │ │
│ │ 录屏 │ Features → Record │ 屏幕录制 │ │
│ │ WiFi 模拟 │ Features → WiFi │ WiFi 连接测试 │ │
│ │ 蓝牙模拟 │ Features → Bluetooth │ 蓝牙连接测试 │ │
│ └─────────────────────┴──────────────────────┴───────────────────┘ │
│ │
│ Simulator 网络调试: │
│ ┌────────────────────────────────────────────────────────────────┐ │
│ │ Network Link Conditioner(网络链接调节器): │ │
│ │ │ │
│ │ 1. Xcode → Window → Devices and Simulators │ │
│ │ 2. 选择模拟器 → 点击 "Show Network Link Conditioner" │ │
│ │ │ │
│ │ 预设配置: │ │
│ │ • No Loss (理想网络) │ │
│ │ • Good 3G │ │
│ │ • Bad 3G │ │
│ │ • Dialup │ │
│ │ • Custom (自定义延迟/带宽/丢包率) │ │
│ │ │ │
│ │ 可调参数: │ │
│ │ • Upload/Download Bandwidth (带宽) │ │
│ │ • Upload/Download Latency (延迟) │ │
│ │ • Upload/Download Packet Loss (丢包率) │ │
│ │ • Upload/Download Jitter (抖动) │ │
│ │ • Packet Corruption (数据包损坏) │ │
│ └────────────────────────────────────────────────────────────────┘ │
└──────────────────────────────────────────────────────────────────────┘16.2 Simulator 与真机调试差异
模拟器 vs 真机调试差异:
┌──────────────────────────────────────────────────────────────────────┐
│ 模拟器 vs 真机对比: │
│ │
│ ┌───────────────────┬──────────────────┬───────────────────────┐ │
│ │ 维度 │ Simulator │ Real Device │ │
│ ├───────────────────┼──────────────────┼───────────────────────┤ │
│ │ 处理器 │ x86_64 / ARM64 │ ARM64 │ │
│ │ 架构模拟 │ x86→ARM 二进制 │ 原生 ARM │ │
│ │ │ 转换(性能损失) │ │ │
│ │ 性能 │ 较快(无硬件限制)│ 真实性能 │ │
│ │ 网络 │ WiFi 模拟 │ 真实网络(3G/4G/5G) │ │
│ │ 传感器 │ 模拟 │ 真实传感器 │ │
│ │ GPS │ 模拟 │ 真实 GPS │ │
│ │ 相机 │ 模拟 │ 真实相机 │ │
│ │ 蓝牙 │ 模拟 │ 真实蓝牙 │ │
│ │ 内存 │ 宿主机器内存 │ 设备物理内存 │ │
│ │ 电池 │ 模拟 │ 真实电池 │ │
│ │ 温度 │ 无 │ 真实温度 │ │
│ │ 崩溃率 │ 低 │ 真实崩溃率 │ │
│ │ App Store 审核 │ 不适用 │ 适用 │ │
│ │ Metal GPU 调试 │ 有限支持 │ 完整支持 │ │
│ │ 推送通知 │ 模拟 │ 真实推送 │ │
│ │ 钥匙串 │ 模拟 │ 真实钥匙串 │ │
│ │ 定位 │ 模拟 │ 真实定位 │ │
│ │ 性能数据准确性 │ 参考性 │ 真实性 │ │
│ └───────────────────┴──────────────────┴───────────────────────┘ │
│ │
│ 调试策略建议: │
│ ┌────────────────────────────────────────────────────────────────┐ │
│ │ • 开发阶段:模拟器快速迭代 │ │
│ │ • 功能测试:真机验证关键功能 │ │
│ │ • 性能测试:真机测试(模拟器性能不代表真机) │ │
│ │ • 网络测试:真机 + Network Link Conditioner │ │
│ │ • 内存测试:真机(模拟器内存不代表真机) │ │
│ │ • Metal GPU 测试:真机(模拟器 Metal 支持有限) │ │
│ │ • 崩溃复现:真机(模拟器崩溃不代表真机会崩溃) │ │
│ │ • 功耗测试:真机(模拟器无功耗概念) │ │
│ └────────────────────────────────────────────────────────────────┘ │
└──────────────────────────────────────────────────────────────────────┘17. 崩溃日志分析
17.1 崩溃日志符号化
崩溃日志符号化流程深度分析:
┌──────────────────────────────────────────────────────────────────────┐
│ 崩溃符号化原理: │
│ │
│ ┌────────────────────────────────────────────────────────────────┐ │
│ │ 崩溃日志结构: │ │
│ │ │ │
│ │ ┌─────────────────────────────────────────────────────┐ │ │
│ │ │ 崩溃报告头部 │ │ │
│ │ │ • 崩溃时间、设备型号、iOS 版本 │ │ │
│ │ │ • UUID(二进制文件标识) │ │ │
│ │ │ • 崩溃类型(EXC_BAD_ACCESS 等) │ │ │
│ │ └─────────────────────────────────────────────────────┘ │ │
│ │ │ │
│ │ ┌─────────────────────────────────────────────────────┐ │ │
│ │ │ 调用栈(Exception Type / Exception Codes) │ │ │
│ │ │ • 崩溃线程(thread N) │ │ │
│ │ │ • 堆栈帧(0x... → symbol + offset) │ │ │
│ │ │ • 寄存器状态 │ │ │
│ │ │ • VM 区域映射(VM Region) │ │ │
│ │ └─────────────────────────────────────────────────────┘ │ │
│ │ │ │
│ │ 符号化依赖: │ │
│ │ • dSYM 文件(包含 DWARF 调试信息) │ │
│ │ • UUID 匹配(崩溃日志 UUID = dSYM UUID) │ │
│ │ • symbolicatecrash 工具 │ │
│ └────────────────────────────────────────────────────────────────┘ │
│ │
│ 符号化方法: │
│ ┌────────────────────────────────────────────────────────────────┐ │
│ │ 方法 1: Xcode 内符号化 │ │
│ │ Xcode → Window → Devices and Simulators │ │
│ │ → 选择设备 → View Device Logs → 选择崩溃报告 → View Report │ │
│ │ │ │
│ │ 方法 2: 命令行符号化 │ │
│ │ symbolicatecrash crash.crash MyApp.app.dSYM > symbolicated.crash │ │
│ │ │ │
│ │ 方法 3: Xcode Organizer │ │
│ │ Xcode → Window → Organizer → Crashes │ │
│ │ → 选择崩溃 → Symbolicate │ │
│ │ │ │
│ │ 方法 4:第三方工具 │ │
│ │ • CrashReporter (GitHub) │ │
│ │ • pycrashreport (Python) │ │
│ │ • symbolicatecrash (Xcode 内置) │ │
│ └────────────────────────────────────────────────────────────────┘ │
│ │
│ dSYM 文件说明: │
│ ┌────────────────────────────────────────────────────────────────┐ │
│ │ dSYM(Debug Symbols): │ │
│ │ • 包含 DWARF 调试信息 │ │
│ │ • 与二进制文件一一对应(UUID 绑定) │ │
│ │ • 存储位置:~/Library/Developer/Xcode/Archives/ │ │
│ │ • 归档中的 dSYM:MyApp.xcarchive/dSYMs/ │ │
│ │ • 从归档提取:xcrun dSYMSummary MyApp.xcarchive │ │
│ │ • 检查 dSYM UUID:dwarfdump --uuid MyApp.app.dSYM │ │
│ │ • 检查崩溃日志 UUID:UUIDs 部分匹配 │ │
│ │ │ │
│ │ 重要:dSYM 必须妥善保存! │ │
│ │ • 发布版本必须保留 dSYM │ │
│ │ • 丢失 dSYM = 无法符号化崩溃报告 │ │
│ │ • 使用 Xcode Archive 自动保存 │ │
│ │ • 使用 fastlane/sigh 自动管理 dSYM │ │
│ └────────────────────────────────────────────────────────────────┘ │
└──────────────────────────────────────────────────────────────────────┘17.2 崩溃类型详解
崩溃类型深度分析:
┌──────────────────────────────────────────────────────────────────────┐
│ 常见崩溃类型及修复方案: │
│ │
│ ┌────────────────┬──────────────────────┬───────────────────────┐ │
│ │ 崩溃类型 │ 原因 │ 修复方案 │ │
│ ├────────────────┼──────────────────────┼───────────────────────┤ │
│ │ EXC_BAD_ACCESS │ 野指针/释放后使用 │ 检查所有权/weak │ │
│ │ │ │ ASan 检测 │ │
│ │ │ │ Zombies 检测 │ │
│ │ │ │ 检查 dealloc │ │
│ │ │ │ 检查 delegate │ │
│ │ │ │ 检查闭包循环引用 │ │
│ │ │ │ │ │
│ │ EXC_CRASH │ 未捕获异常/SIGKILL │ 检查 NSException │ │
│ │ │ │ 检查 uncaughtException│ │
│ │ │ │ 检查 SIGKILL 原因 │ │
│ │ │ │ (内存不足/超时) │ │
│ │ │ │ │ │
│ │ EXC_ARITHMETIC │ 除以零/整数溢出 │ 安全检查 │ │
│ │ │ │ 使用 checked 操作 │ │
│ │ │ │ 范围检查 │ │
│ │ │ │ │ │
│ │ EXC_BREAKPOINT │ breakpoint/断言失败 │ 条件检查 │ │
│ │ │ │ 移除调试断点 │ │
│ │ │ │ 安全检查断言 │ │
│ │ │ │ │ │
│ │ SIGABRT │ uncaughtException │ 异常处理 │ │
│ │ │ │ 检查 JSON 解析 │ │
│ │ │ │ 检查参数有效性 │ │
│ │ │ │ │ │
│ │ SIGSEGV │ 数组越界/内存越界 │ ASan 检测 │ │
│ │ │ │ 边界检查 │ │
│ │ │ │ 检查指针运算 │ │
│ │ │ │ │ │
│ │ SIGBUS │ 对齐错误/内存映射 │ 检查内存对齐 │ │
│ │ │ │ 检查 mmap │ │
│ │ │ │ │ │
│ │ SIGILL │ 非法指令 │ 检查字节序 │ │
│ │ │ │ 检查汇编代码 │ │
│ │ │ │ │ │
│ │ KERN_PROTECTION│ 内存访问权限 │ 检查权限位 │ │
│ │ │ │ 检查 mprotect │ │
│ │ │ │ │ │
│ └────────────────┴──────────────────────┴───────────────────────┘ │
│ │
│ EXC_BAD_ACCESS 深度分析: │
│ ┌────────────────────────────────────────────────────────────────┐ │
│ │ EXC_BAD_ACCESS 子类型: │ │
│ │ • KERN_INVALID_ADDRESS → 无效地址(野指针) │ │
│ │ • KERN_PROTECTION_FAILURE → 权限不足 │ │
│ │ • EXC_ARM_DA_ALIGN → 未对齐访问 │ │
│ │ │ │
│ │ 常见原因: │ │
│ │ 1. 释放后使用(use-after-free) │ │
│ │ 2. 野指针(悬垂指针) │ │
│ │ 3. 数组越界 │ │
│ │ 4. 未初始化指针 │ │
│ │ 5. 内存越界 │ │
│ │ 6. 访问已释放的内存 │ │
│ │ 7. 多线程数据竞争 │ │
│ │ │ │
│ │ 排查方法: │ │
│ │ • ASan → 精确定位 │ │
│ │ • Zombies → 确定释放时间 │ │
│ │ • TSan → 检查数据竞争 │ │
│ │ • 崩溃报告调用栈 → 定位崩溃位置 │ │
│ └────────────────────────────────────────────────────────────────┘ │
└──────────────────────────────────────────────────────────────────────┘17.3 崩溃报告解析
崩溃报告完整解析:
┌──────────────────────────────────────────────────────────────────────┐
│ 崩溃报告(.crash 文件)解析: │
│ │
│ 崩溃报告结构: │
│ ┌────────────────────────────────────────────────────────────────┐ │
│ │ 1. 头部信息(OS Version / Device Model / Date/Time) │ │
│ │ 2. 崩溃类型(Exception Type / Codes) │ │
│ │ 3. 崩溃线程(thread N Crashed) │ │
│ │ 4. 调用栈(Binary Images / VM Regions) │ │
│ │ 5. 寄存器状态(All Registers / Native) │ │
│ │ 6. 二进制镜像列表(Binary Images / UUID 匹配) │ │
│ │ 7. VM 区域映射(VM Region Summary) │ │
│ │ 8. 扩展信息(Incident Identifier / CrashReporter Key) │ │
│ └────────────────────────────────────────────────────────────────┘ │
│ │
│ 调用栈分析步骤: │
│ ┌────────────────────────────────────────────────────────────────┐ │
│ │ 步骤 1: 定位崩溃线程 │ │
│ │ Thread N Crashed │ │
│ │ 0 MyApp 0x0000000100abc123 MyClass.method + 123 │ │
│ │ 1 MyApp 0x0000000100def456 MyClass.otherMethod + 88 │ │
│ │ 2 MyApp 0x0000000100123789 MyClass.anotherMethod + 45 │ │
│ │ ... │ │
│ │ │ │
│ │ 步骤 2: 符号化调用栈 │ │
│ │ 0 MyClass.method(MyClass.swift:42) ← 崩溃点 │ │
│ │ 1 MyClass.otherMethod(MyClass.swift:100) │ │
│ │ 2 MyClass.anotherMethod(MyClass.swift:200) │ │
│ │ │ │
│ │ 步骤 3: 分析崩溃原因 │ │
│ │ • 检查崩溃行代码 │ │
│ │ • 检查该行的变量状态 │ │
│ │ • 检查调用链中的错误传播 │ │
│ │ • 检查资源释放时机 │ │
│ └────────────────────────────────────────────────────────────────┘ │
│ │
│ 崩溃报告中的 Binary Images 解析: │
│ ┌────────────────────────────────────────────────────────────────┐ │
│ │ Binary Images: │ │
│ │ 0x100000000 - 0x100ffffff MyApp arm64 <UUID> MyApp.app │ │
│ │ 0x180000000 - 0x1801fffff UIKit arm64 <UUID> /usr/lib/... │ │
│ │ 0x1a0000000 - 0x1a05fffff libswiftCore.dylib │ │
│ │ ... │ │
│ │ │ │
│ │ 用途: │ │
│ │ • UUID 匹配 dSYM 文件 │ │
│ │ • 确定崩溃发生在哪个模块 │ │
│ │ • 区分框架代码 vs 自身代码 │ │
│ └────────────────────────────────────────────────────────────────┘ │
└──────────────────────────────────────────────────────────────────────┘18. Sanitizer 运行时检测
18.1 Address Sanitizer (ASan)
ASan 原理深度分析:
┌──────────────────────────────────────────────────────────────────────┐
│ Address Sanitizer (ASan) 原理: │
│ │
│ ┌────────────────────────────────────────────────────────────────┐ │
│ │ ASan 工作原理: │ │
│ │ │ │
│ │ 1. 编译期插桩: │ │
│ │ • 在每个 malloc/free/new/delete 周围插入检查代码 │ │
│ │ • 在每个内存访问(load/store)周围插入检查代码 │ │
│ │ • 在每个函数调用周围插入 red zone 检查 │ │
│ │ │ │
│ 2. 运行时检测: │ │
│ │ • malloc/free 时修改内存页权限(shadow memory) │ │
│ │ • 内存访问时检查 shadow memory │ │
│ │ • 发现越界/使用后释放时触发崩溃 │ │
│ │ │ │
│ │ Shadow Memory(阴影内存): │ │
│ │ • 每 8 字节的用户内存对应 1 字节的 shadow memory │ │
│ │ • Shadow 值含义: │ │
│ │ • 0 → 可访问的内存 │ │
│ │ • 1-127 → 剩余的字节数(部分可用) │ │
│ │ • 128-251 → 已释放的堆内存 │ │
│ │ • 252-253 → 已释放的栈内存 │ │
│ │ • 254 → 已释放的全局/静态内存 │ │
│ │ • 255 → 红色区域(guard zone) │ │
│ │ │ │
│ │ ASan 检测的错误类型: │ │
│ │ ┌──────────────────────────────────────────────────────┐ │ │
│ │ │ • 堆使用后释放 (heap-use-after-free) │ │ │
│ │ │ • 栈使用后释放 (stack-use-after-return) │ │ │
│ │ │ • 全局使用后释放 (global-use-after-free) │ │ │
│ │ │ • 堆越界写 (heap-buffer-overflow) │ │ │
│ │ │ • 堆越界读 (heap-buffer-overflow read) │ │ │
│ │ │ • 栈越界 (stack-buffer-overflow) │ │ │
│ │ │ • 全局越界 (global-buffer-overflow) │ │ │
│ │ │ • 重复释放 (double-free) │ │ │
│ │ │ • 内存泄漏 (leak - 需要 LeakSanitizer) │ │ │
│ │ │ • 初始化顺序 (init-order) │ │ │
│ │ │ • 比较大小 (sizeof-compare) │ │ │
│ │ └──────────────────────────────────────────────────────┘ │ │
│ └────────────────────────────────────────────────────────────────┘ │
│ │
│ ASan 启用与配置: │
│ ┌────────────────────────────────────────────────────────────────┐ │
│ │ 启用方式: │ │
│ │ Xcode → Scheme → Diagnostics → Enable Address Sanitizer │ │
│ │ │ │
│ │ 环境变量配置: │ │
│ │ ASAN_OPTIONS=detect_stack_use_after_return=1 │ │
│ │ ASAN_OPTIONS=detect_leaks=1 │ │
│ │ ASAN_OPTIONS=print_stats=1 │ │
│ │ ASAN_OPTIONS=check_initialization_order=1 │ │
│ │ ASAN_OPTIONS=strict_string_checks=1 │ │
│ │ ASAN_OPTIONS=detect_odr_violation=1 │ │
│ │ ASAN_OPTIONS=halt_on_error=1 │ │
│ │ ASAN_OPTIONS=symbolize=1 │ │
│ │ ASAN_OPTIONS=verbosity=1 │ │
│ └────────────────────────────────────────────────────────────────┘ │
│ │
│ ASan 性能开销: │
│ ┌────────────────────────────────────────────────────────────────┐ │
│ │ 指标 │ 开销 │ │
│ ├─────────────────┼──────────────────────────────────────────────┤ │
│ │ CPU 开销 │ 2x-8x(取决于检测类型) │ │
│ │ 内存开销 │ 2x(shadow memory) │ │
│ │ 启动延迟 │ 较高(初始检测) │ │
│ │ 崩溃概率 │ 高(覆盖所有内存错误) │ │
│ │ 检测精度 │ 高(精确到字节) │ │
│ │ 误报率 │ 极低 │ │
│ │ 适用场景 │ 开发/测试阶段 │ │
│ │ 生产可用 │ 否 │ │
│ └─────────────────┴──────────────────────────────────────────────┘ │
└──────────────────────────────────────────────────────────────────────┘18.2 Thread Sanitizer (TSan)
Thread Sanitizer (TSan) 原理:
┌──────────────────────────────────────────────────────────────────────┐
│ Thread Sanitizer (TSan) 原理: │
│ │
│ ┌────────────────────────────────────────────────────────────────┐ │
│ │ TSan 工作原理: │ │
│ │ │ │
│ │ 1. 编译期插桩: │ │
│ │ • 在每个锁操作(pthread_mutex_lock/unlock)周围插入代码 │ │
│ │ • 在每个内存读写操作周围插入代码 │ │
│ │ • 在每个线程创建/销毁周围插入代码 │ │
│ │ │ │
│ │ 2. 运行时检测: │ │
│ │ • 维护 Happens-Before 关系图 │ │
│ │ • 检测数据竞争(Data Race) │ │
│ │ • 检测锁顺序违反(Lock Order) │ │
│ │ • 检测锁释放后访问(Lock After Unlock) │ │
│ │ │ │
│ │ TSan 检测的问题类型: │ │
│ │ ┌──────────────────────────────────────────────────────┐ │ │
│ │ │ • 数据竞争 (Data Race) │ │ │
│ │ │ → 多个线程同时访问同一内存,至少一个是写操作 │ │ │
│ │ │ • 锁顺序违反 (Lock Order Violation) │ │ │
│ │ │ → 锁 A 和锁 B 以不同顺序获取 → 死锁风险 │ │ │
│ │ │ • 锁释放后访问 (Lock After Unlock) │ │ │
│ │ │ → 解锁后仍然访问被锁保护的内存 │ │ │
│ │ │ • 释放后获取 (Acquire After Release) │ │ │
│ │ │ → 先 unlock 再 lock 同一锁 │ │ │
│ │ │ • 内存泄漏 (Memory Leak - 部分支持) │ │ │
│ │ └──────────────────────────────────────────────────────┘ │ │
│ └────────────────────────────────────────────────────────────────┘ │
│ │
│ TSan 启用与配置: │
│ ┌────────────────────────────────────────────────────────────────┐ │
│ │ 启用方式: │ │
│ │ Xcode → Scheme → Diagnostics → Enable Thread Sanitizer │ │
│ │ │ │
│ │ 常见数据竞争场景: │ │
│ │ // ❌ 数据竞争:两个 Task 同时访问非线程安全的数组 │ │
│ │ let shared = MySharedObject() │ │
│ │ Task { │ │
│ │ shared.data.append("A") // 可能数据竞争 │ │
│ │ } │ │
│ │ Task { │ │
│ │ shared.data.append("B") // 可能数据竞争 │ │
│ │ } │ │
│ │ │ │
│ │ // ✅ 修复:用 Actor 保证线程安全 │ │
│ │ actor SharedData { │ │
│ │ var data: [String] = [] │ │
│ │ func append(_ s: String) { data.append(s) } │ │
│ │ } │ │
│ │ │ │
│ │ // ✅ 修复:用 DispatchQueue 串行化 │ │
│ │ let queue = DispatchQueue(label: "com.app.shared") │ │
│ │ queue.async { self.data.append("A") } │ │
│ │ queue.async { self.data.append("B") } │ │
│ │ │ │
│ │ // ✅ 修复:用 os_unfair_lock │ │
│ │ var lock = os_unfair_lock() │ │
│ │ os_unfair_lock(&lock) │ │
│ │ self.data.append("A") │ │
│ │ os_unfair_unlock(&lock) │ │
│ └────────────────────────────────────────────────────────────────┘ │
│ │
│ TSan 性能开销: │
│ ┌────────────────────────────────────────────────────────────────┐ │
│ │ 指标 │ 开销 │ │
│ ├─────────────────┼──────────────────────────────────────────────┤ │
│ │ CPU 开销 │ 10x-15x(较高) │ │
│ │ 内存开销 │ 2x-4x(追踪表) │ │
│ │ 启动延迟 │ 较高 │ │
│ │ 检测精度 │ 高(基于 Happens-Before) │ │
│ │ 误报率 │ 低(TSan 设计减少了误报) │ │
│ │ 适用场景 │ 开发/测试阶段 │ │
│ │ 生产可用 │ 否 │ │
│ └─────────────────┴──────────────────────────────────────────────┘ │
└──────────────────────────────────────────────────────────────────────┘18.3 UBSan 与 ASan/TSan 对比
UBSan 与 ASan/TSan 对比:
┌──────────────────────────────────────────────────────────────────────┐
│ Sanitizer 全景对比: │
│ │
│ ┌───────────────────┬─────────────────────┬───────────────┬───────┐ │
│ │ 特性 │ ASan │ TSan │ UBSan │ │
│ ├───────────────────┼─────────────────────┼───────────────┼───────┤ │
│ │ 检测类型 │ 内存错误 │ 线程错误 │ UB │ │
│ │ 检测内容 │ 越界/使用后释放等 │ 数据竞争 │ 未定义行为 │
│ │ 性能开销 │ 2x-8x │ 10x-15x │ 1x-3x │ │
│ │ 内存开销 │ 2x (shadow) │ 2x-4x (track) │ 低 │ │
│ │ 编译期开销 │ 中 │ 高 │ 低 │ │
│ │ 检测精度 │ 高(字节级) │ 高(H-B关系) │ 中 │ │
│ │ 误报率 │ 极低 │ 低 │ 低 │ │
│ │ 检测时机 │ 运行时 │ 运行时 │ 运行时 │ │
│ │ Swift 支持 │ ✅ │ ✅ │ ✅ │ │
│ │ Objective-C 支持 │ ✅ │ ✅ │ ⚠️ │ │
│ │ C/C++ 支持 │ ✅ │ ✅ │ ✅ │ │
│ │ 生产可用 │ ❌ │ ❌ │ ⚠️ │ │
│ │ Xcode 集成 │ 一键启用 │ 一键启用 │ 可选 │ │
│ └───────────────────┴─────────────────────┴───────────────┴───────┘ │
│ │
│ UBSan(UndefinedBehaviorSanitizer): │
│ ┌────────────────────────────────────────────────────────────────┐ │
│ │ UBSan 检测内容: │ │
│ │ • 整数溢出(signed integer overflow) │ │
│ │ • 除以零 │ │
│ │ • 数组索引越界 │ │
│ │ • 未初始化变量使用 │ │
│ │ • 空指针解引用 │ │
│ │ • 对齐错误 │ │
│ │ • 类型双关(type punning) │ │
│ │ • 函数参数超出范围 │ │
│ │ • 枚举值超出范围 │ │
│ │ • 未对齐指针访问 │ │
│ │ • 无效的内存访问(aligned/unaligned access) │ │
│ │ • 虚函数调用(虚表指针无效) │ │
│ │ • 无符号整数溢出 │ │
│ │ • 有符号整数溢出 │ │
│ │ • 移位溢出 │ │
│ │ • 无效类型转换 │ │
│ │ • 未定义的行为(strict aliasing) │ │
│ │ │ │
│ │ UBSan 使用方式: │ │
│ │ Xcode → Build Settings → clang - UndefinedBehaviorSanitizer │ │
│ │ ───> YES │ │
│ │ │ │
│ │ UBSan 特点: │ │
│ │ • 开销最低(相比 ASan/TSan) │ │
│ │ • 可检测编译期无法发现的运行时 UB │ │
│ │ • 某些 UB 在生产环境也可以启用 │ │
│ │ • 不能检测所有 UB(部分需要编译期分析) │ │
│ └────────────────────────────────────────────────────────────────┘ │
└──────────────────────────────────────────────────────────────────────┘19. 内存调试实战
19.1 内存调试全栈方法
内存调试完整方法论:
┌──────────────────────────────────────────────────────────────────────┐
│ 内存调试分层策略: │
│ │
│ ┌────────────────────────────────────────────────────────────────┐ │
│ │ Layer 1: 静态分析(编译期) │ │
│ │ • Xcode Analyzer(⌘ + Shift + B) │ │
│ │ • Swift Compiler Warnings │ │
│ │ • 循环引用静态检查 │ │
│ │ • 未使用变量警告 │ │
│ │ • 未初始化变量警告 │ │
│ │ │ │
│ │ Layer 2: 编译期插桩(构建期) │ │
│ │ • Address Sanitizer(ASan) │ │
│ │ • Thread Sanitizer(TSan) │ │
│ │ • UBSan(UBSanitizer) │ │
│ │ • 编译时内存管理检查 │ │
│ │ │ │
│ │ Layer 3: 运行时调试(开发阶段) │ │
│ │ • LLDB 变量检查 │ │
│ │ • 断点调试 │ │
│ │ • 内存断点(Watchpoint) │ │
│ │ • 表达式求值(修改变量/调用方法) │ │
│ │ │ │
│ │ Layer 4: 性能分析(测试阶段) │ │
│ │ • Allocations(内存分配追踪) │ │
│ │ • Leaks(内存泄漏检测) │ │
│ │ • Time Profiler(CPU 分析) │ │
│ │ • Core Animation(渲染分析) │ │
│ │ │ │
│ │ Layer 5: 生产监控(发布后) │ │
│ │ • Crashlytics(崩溃报告) │ │
│ │ • Memory Profiling(远程内存分析) │ │
│ │ • ANR 监控(Not-Responding) │ │
│ │ • 自定义内存指标 │ │
│ └────────────────────────────────────────────────────────────────┘ │
│ │
│ 内存调试完整流程: │
│ ┌────────────────────────────────────────────────────────────────┐ │
│ │ 1. 发现内存问题 │ │
│ │ • 内存增长(Allocations 观察) │ │
│ │ • 内存泄漏(Leaks 检测) │ │
│ │ • 野指针崩溃(Zombies/ASan) │ │
│ │ • 卡顿(Time Profiler) │ │
│ │ │ │
│ │ 2. 定位问题 │ │
│ │ • Allocations Snapshot 对比 │ │
│ │ • Leaks 调用栈分析 │ │
│ │ • ASan/TSan 崩溃报告 │ │
│ │ • LLDB 变量检查 │ │
│ │ │ │
│ │ 3. 修复问题 │ │
│ │ • 打破循环引用 │ │
│ │ • 正确释放资源 │ │
│ │ • 添加边界检查 │ │
│ │ • 优化分配模式 │ │
│ │ │ │
│ │ 4. 验证修复 │ │
│ │ • 重新 Profile 对比 │ │
│ │ • ASan/TSan 验证 │ │
│ │ • 回归测试 │ │
│ └────────────────────────────────────────────────────────────────┘ │
└──────────────────────────────────────────────────────────────────────┘19.2 实战案例
内存调试实战案例:
┌──────────────────────────────────────────────────────────────────────┐
│ 案例 1:列表滚动内存泄漏 │
│ │
│ 问题描述:滑动列表时内存持续增长 │
│ │
│ 排查步骤: │
│ ┌────────────────────────────────────────────────────────────────┐ │
│ │ 1. Allocations → Record │ │
│ │ 2. Snapshot A → 滑动 50 次 │ │
│ │ 3. Snapshot B → 滑动 100 次 │ │
│ │ 4. 对比 A→B → [MyCell] 增长 500KB │ │
│ │ 5. 双击 [MyCell] → 查看调用栈 │ │
│ │ 6. 发现 cellForRowAt 中创建的新对象未释放 │ │
│ │ 7. 检查闭包捕获 self → weak self 修复 │ │
│ └────────────────────────────────────────────────────────────────┘ │
│ │
│ 代码修复: │
│ // ❌ 泄漏:cellForRowAt 中创建闭包捕获 self │ │
│ func tableView(_ tableView: UITableView, │ │
│ cellForRowAt indexPath: IndexPath) -> UITableViewCell │ │
│ let cell = tableView.dequeueReusableCell(...) │ │
│ cell.titleLabel.text = item.title │ │
│ cell.onTap = { [self] in // 💥 循环引用 │ │
│ self.handleTap(item) │ │
│ } │ │
│ return cell │ │
│ │ │
│ // ✅ 修复:weak self │ │
│ cell.onTap = { [weak self] in // ✅ │ │
│ self?.handleTap(item) │ │
│ } │ │
│ └────────────────────────────────────────────────────────────────┘ │
└──────────────────────────────────────────────────────────────────────┘19.3 实战案例(续)
内存调试实战案例(续):
┌──────────────────────────────────────────────────────────────────────┐
│ 案例 2:页面切换内存泄漏 │
│ │
│ 问题描述:在两个 VC 间切换,内存持续增长 │
│ │
│ 排查步骤: │
│ ┌────────────────────────────────────────────────────────────────┐ │
│ │ 1. Allocations → Record │ │
│ │ 2. 对比切换前后的内存快照 │ │
│ │ 3. 发现 [MyViewController] 实例数持续增长 │ │
│ │ 4. Leaks → 确认泄漏 │ │
│ │ 5. Leaks → 双击泄漏 → 查看调用栈 │ │
│ │ 6. 发现 NavigationController 的 viewControllers 数组持有引用 │ │
│ │ 7. 发现闭包捕获 self → 循环引用 │ │
│ └────────────────────────────────────────────────────────────────┘ │
│ │
│ 代码修复: │ │
│ // ❌ 泄漏:闭包中 self 被 Timer 和 notification 同时捕获 │ │
│ class MyViewController: UIViewController { │ │
│ var timer: Timer? │ │
│ override func viewDidLoad() { │ │
│ super.viewDidLoad() │ │
│ timer = Timer.scheduledTimer( │ │
│ withTimeInterval: 1.0, │ │
│ repeats: true, │ │
│ target: self, // 💥 Timer 强引用 self │ │
│ selector: #selector(update), │ │
│ userInfo: nil, │ │
│ repeats: true │ │
│ ) │ │
│ NotificationCenter.default.addObserver( │ │
│ self, // 💥 通知强引用 self │ │
│ selector: #selector(didReceive), │ │
│ name: .MyEvent, │ │
│ object: nil │ │
│ ) │ │
│ } │ │
│ deinit { │ │
│ timer?.invalidate() │ │
│ NotificationCenter.default.removeObserver(self) │ │
│ } │ │
│ } │ │
│ │
│ // ✅ 修复:weak self + 正确的清理 │ │
│ override func viewDidLoad() { │ │
│ super.viewDidLoad() │ │
│ timer = Timer.scheduledTimer( │ │
│ withTimeInterval: 1.0, │ │
│ repeats: true) { [weak self] _ in │ │
│ self?.update() │ │
│ } │ │
│ notificationObserver = NotificationCenter.default │ │
│ .addObserver(forName: .MyEvent, │ │
│ object: nil, │ │
│ queue: .main) { [weak self] _ in │ │
│ self?.didReceive() │ │
│ } │ │
│ } │ │
│ deinit { │ │
│ if let observer = notificationObserver { │ │
│ NotificationCenter.default.removeObserver(observer) │ │
│ } │ │
│ } │ │
└──────────────────────────────────────────────────────────────────────┘19.4 内存调试工具对比
内存调试工具对比表:
┌──────────────────┬───────────────┬───────────────┬───────────────┬───────────────┐
│ 工具 │ 检测精度 │ 性能开销 │ 检测内容 │ 适用阶段 │
├──────────────────┼───────────────┼───────────────┼───────────────┼───────────────┤
│ Allocations │ 高 │ 中 │ 内存分配/释放 │ 开发/测试 │
│ Leaks │ 中高 │ 低 │ 内存泄漏 │ 开发/测试 │
│ Zombies │ 高 │ 高 │ 野指针 │ 测试阶段 │
│ ASan │ 极高(字节级) │ 高(2x-8x) │ 内存错误 │ 测试阶段 │
│ TSan │ 高 │ 高(10x-15x) │ 数据竞争 │ 测试阶段 │
│ UBSan │ 中 │ 低(1x-3x) │ UB │ 开发/测试 │
│ Xcode Memory │ 中高 │ 低 │ 内存布局可视化 │ 调试阶段 │
│ Graph Debugger │ 中 │ 中 │ 对象图/引用 │ 调试阶段 │
│ LeakCanary │ 高 │ 低 │ Android 泄漏 │ Android │
│ 自定义 retain │ 中 │ 低 │ 引用计数追踪 │ 开发阶段 │
│ Instruments VM │ 中高 │ 低 │ 虚拟内存 │ 测试阶段 │
│ Memory Graph │ 中 │ 低 │ 对象引用关系 │ 调试阶段 │
└──────────────────┴───────────────┴───────────────┴───────────────┴───────────────┘20. 调试与 Android/Kotlin 对比
20.1 调试工具链对比
iOS 与 Android/Kotlin 调试工具链对比:
┌──────────────────────────────┬───────────────────────┬───────────────────────┐
│ 维度 │ iOS │ Android/Kotlin │
├──────────────────────────────┼───────────────────────┼───────────────────────┤
│ 集成调试器 │ LLDB │ LLDB (NDK) / GDB │
│ IDE 调试入口 │ Xcode Debug Navigator │ Android Studio Debugger│
│ 断点管理 │ Breakpoint Navigator │ Breakpoint Dialog │
│ 条件断点 │ ✅ 表达式条件 │ ✅ 表达式条件 │
│ 日志断点 │ ✅ breakpoint command │ ✅ Logcat Filter │
│ 异常断点 │ ✅ Exception BP │ ✅ Exception BP │
│ 观察断点 (Watchpoint) │ ✅ HW debug regs │ ❌ (需源码修改) │
│ 内存分析工具 │ Allocations + Leaks │ Android Studio Memory │
│ CPU 分析工具 │ Time Profiler │ CPU Profiler │
│ 渲染分析工具 │ Core Animation │ Layout Inspector │
│ 网络分析工具 │ Network │ Network Profiler │
│ GPU 分析工具 │ Metal Shader │ GPU Profiler │
│ 线程分析工具 │ Thread Profiler │ Thread Profiler │
│ 崩溃分析 │ symbolicatecrash + │ asecrash/symbolicate │
│ │ dSYM │ │
│ 内存泄漏检测 │ Allocations/Leaks │ LeakCanary │
│ 地址安全检测 (ASan) │ ✅ Xcode Scheme │ ✅ ndk-build CMake │
│ 线程安全检测 (TSan) │ ✅ Xcode Scheme │ ✅ ndk-build CMake │
│ 未定义行为检测 (UBSan) │ ✅ Xcode Scheme │ ✅ ndk-build CMake │
│ 设备日志 │ Console │ Logcat │
│ 模拟器 │ Simulator │ Android Emulator │
│ 网络模拟 │ Network Link Cond. │ Network Link Cond. │
│ 性能 profiling │ Instruments │ Android Studio Profiler│
│ 热修复 │ ❌ (无官方支持) │ Hotfix (ClassLink) │
│ 远程调试 │ Xcode Remote Debug │ adb forward / Chrome │
│ 崩溃上报 │ Crashlytics │ Firebase Crashlytics │
│ 内存图 │ Memory Graph Debugger │ Object Inspector │
└──────────────────────────────┴───────────────────────┴───────────────────────┘20.2 LLDB vs GDB/LLDB (Android) 对比
LLDB 在 iOS 与 Android 中的对比:
┌──────────────────────────────┬───────────────────────┬───────────────────────┐
│ 特性 │ iOS (LLDB) │ Android (LLDB) │
├──────────────────────────────┼───────────────────────┼───────────────────────┤
│ 默认调试器 │ LLDB │ LLDB (NDK) / GDB │
│ 进程控制机制 │ ptrace + Mach Exception│ ptrace + siginfo │
│ 架构支持 │ ARM64 / x86_64 │ ARM / ARM64 / x86 │
│ 动态库加载钩子 │ dyld helper │ linker hook (Android) │
│ 符号化方式 │ DWARF + dSYM │ DWARF + .so symbols │
│ Swift 支持 │ ✅ 完整支持 │ ❌ 无 (仅 C/C++) │
│ Objective-C 支持 │ ✅ 完整支持 │ ⚠️ 有限支持 │
│ JNI 支持 │ ❌ │ ✅ (NDK) │
│ C/C++ 支持 │ ✅ │ ✅ (NDK) │
│ 内存分析 │ Allocations/Leaks │ Memory Profiler │
│ 崩溃分析 │ symbolicatecrash │ asecrash │
│ Sanitizer 支持 │ ASan/TSan/UBSan │ ASan/TSan/UBSan │
│ 性能分析 │ Instruments │ perfetto/Android Profiler│
│ 远程调试 │ Xcode Remote │ adb forward + GDB │
│ GUI 集成 │ Xcode Debug Navigator │ Android Studio Debugger│
└──────────────────────────────┴───────────────────────┴───────────────────────┘20.3 Instruments vs Android Profiler 对比
Instruments vs Android Profiler 对比:
┌──────────────────┬───────────────────────┬───────────────────────┐
│ 功能 │ iOS Instruments │ Android Profiler │
├──────────────────┼───────────────────────┼───────────────────────┤
│ CPU 分析 │ Time Profiler │ CPU Profiler │
│ 原理 │ 采样 (sampling) │ 采样 (sampling) │
│ 火焰图 │ ✅ │ ✅ │
│ 函数耗时 │ ✅ │ ✅ │
│ 调用链分析 │ ✅ │ ✅ │
│ 主线程监控 │ ✅ │ ✅ │
│ 异步任务分析 │ ✅ │ ✅ │
│ │ │ │
│ 内存分析 │ Allocations + Leaks │ Memory Profiler │
│ 内存分配追踪 │ ✅ │ ✅ │
│ 内存泄漏检测 │ ✅ │ ✅ (LeakCanary) │
│ 对象图 │ ✅ (Graph Debugger) │ ✅ (Object Inspector) │
│ GC 分析 │ ❌ (ARC) │ ✅ (Garbage Collection)│
│ 内存峰谷 │ ✅ │ ✅ │
│ │ │ │
│ 渲染分析 │ Core Animation │ Layout Inspector │
│ GPU 分析 │ Metal Shader │ GPU Profiler │
│ 离屏渲染检测 │ ✅ │ ⚠️ 间接支持 │
│ 帧率监控 │ ✅ │ ✅ │
│ │ │ │
│ 网络分析 │ Network │ Network Profiler │
│ HTTP 请求追踪 │ ✅ │ ✅ │
│ WebSocket 追踪 │ ✅ | ✅ │
│ 延迟分析 │ ✅ │ ✅ │
│ │ │ │
│ 能耗分析 │ Energy Log │ Battery Profiler │
│ CPU 能耗 │ ✅ │ ✅ │
│ 网络能耗 │ ✅ │ ✅ │
│ 屏幕能耗 │ ✅ │ ✅ │
│ │ │ │
│ 系统追踪 │ System Trace │ Perfetto │
│ 内核事件 │ ✅ │ ✅ │
│ 进程调度 │ ✅ │ ✅ │
│ I/O 追踪 │ ✅ │ ✅ │
│ 帧渲染 │ ✅ │ ✅ │
└──────────────────┴───────────────────────┴───────────────────────┘20.4 崩溃日志分析对比
崩溃日志分析:iOS vs Android
┌──────────────────────────────┬───────────────────────┬───────────────────────┐
│ 维度 │ iOS │ Android │
├──────────────────────────────┼───────────────────────┼───────────────────────┤
│ 崩溃文件格式 │ .crash (UUID 匹配) │ .txt / .log │
│ 符号文件 │ dSYM (DWARF) │ .so symbols │
│ 符号化工具 │ symbolicatecrash | asecrash/symbolicate │
│ dSYM 管理 │ Xcode Archive 自动保存 │ 需手动保存 │
│ UUID 匹配 │ ✅ │ ✅ │
│ Native 崩溃 │ ✅ (Mach Exception) │ ✅ (Signal) │
│ Java/Kotlin 崩溃 │ ❌ | ✅ (Throwable) │
│ Objective-C 异常 │ ✅ (NSException) | ❌ |
│ JNI 崩溃 | ⚠️ 部分支持 │ ✅ │
│ 调用栈格式 │ 符号+offset │ 符号+offset │
│ 寄存器信息 | ✅ │ ✅ │
│ VM 区域信息 │ ✅ │ ✅ │
│ 崩溃原因分类 │ EXC_BAD_ACCESS 等 │ SIGABRT/SIGSEGV 等 │
│ 自动收集 │ Crashlytics │ Firebase Crashlytics │
│ 远程符号化 | ✅ (Firebase) │ ✅ (Firebase) │
└──────────────────────────────┴───────────────────────┴───────────────────────┘20.5 Logcat vs Console 对比
日志系统:Logcat (Android) vs Console (iOS)
┌──────────────────────────────┬───────────────────────┬───────────────────────┐
│ 维度 │ iOS Console │ Android Logcat │
├──────────────────────────────┼───────────────────────┼───────────────────────┤
│ 日志来源 | 系统 + 应用 │ 系统 + 应用 │
│ 查看方式 | Xcode Console │ Android Studio Logcat │
│ 实时输出 | ✅ │ ✅ │
│ 历史日志 | ✅ (保存 .txt) │ ✅ (保存 .txt) │
│ 过滤 | ✅ (搜索/标签) │ ✅ (Tag/Level/PID) │
│ 崩溃日志 | ✅ (自动捕获) │ ✅ (自动捕获) │
│ 自定义日志级别 | NSLog/OSLog │ Log.d/w/e/i/v │
│ 结构化日志 | OSLog (OSLogSource) │ SLF4J/Kotlin Logging │
│ 异步日志 | ✅ │ ✅ │
│ 性能分析 | Instruments │ Android Profiler │
│ 设备日志导出 | Console.app | logcat > file.txt │
│ 远程日志 | Xcode Remote | adb logcat -d │
│ 日志持久化 | Console.app 自动保存 | logcat 配置文件 │
└──────────────────────────────┴───────────────────────┴───────────────────────┘21. 面试考点汇总
21.1 高频面试题
基础题
1. LLDB 常用调试命令?
答:
• po/p:打印对象/值
• breakpoint set/clear/delete:断点管理
• n/s/c:执行控制(next/step/continue)
• fr var:查看帧变量
• watchpoint:内存断点
• bt/backtrace:堆栈回溯
• memory read/x:内存查看
• expr/e:表达式求值
2. Instruments 主要工具?
答:
• Time Profiler:CPU 时间分析(采样原理)
• Allocations:内存分配追踪(Snapshot 机制)
• Leaks:内存泄漏检测(标记-清除算法)
• Zombies:悬垂指针检测(对象替换机制)
• Core Animation:动画性能(离屏渲染检测)
• System Trace:系统级追踪
3. 内存泄漏常见原因?
答:
• 闭包循环引用([weak self]/[unowned self])
• NSTimer/KVO/NotificationCenter 未清理
• CADisplayLink 未 invalidate
• Delegate 未置 nil
• 缓存未限制大小
• 异步任务持有 self 引用
4. ASan 和 TSan 的区别?
答:
• ASan:检测内存错误(越界、使用后释放等),通过 shadow memory
• TSan:检测数据竞争(多线程同时访问),通过 Happens-Before
• ASan 开销 2x-8x,TSan 开销 10x-15x
• 两者都可在 Xcode 中一键启用
5. 如何分析崩溃日志?
答:
• 通过 dSYM 文件符号化(UUID 匹配)
• symbolicatecrash 命令行工具
• 关注崩溃类型和调用栈
• 常见崩溃:EXC_BAD_ACCESS、SIGABRT、EXC_CRASH进阶题
6. LLDB 断点的底层实现原理?
答:
• x86_64:第一条指令替换为 0xCC (INT3)
• ARM64:第一条指令替换为 0xD4200000 (BRK #0x1)
• 触发 SIGTRAP → 内核通过 ptrace 通知 LLDB
• LLDB 暂停进程,显示上下文
• 用户点击 continue → LLDB 恢复原始指令
7. Allocations 中 Snapshot 的作用?
答:
• 创建内存快照(Snapshot A → Snapshot B)
• 对比两次快照的内存差异(Byte Increase)
• 定位内存增长源头
• 识别循环引用(Retain Cycle 检测)
8. Zombies 工具的工作原理?
答:
• 拦截 -release,不 dealloc,改为"僵尸对象"
• 设置 isa 指向 _NSZombie_<ClassName>
• 当僵尸对象收到消息时,打印崩溃信息
• 输出:Message sent to deallocated instance
9. Time Profiler 的采样原理?
答:
• 每隔 1ms(1000Hz)发送 SIGPROF 信号
• 内核暂停进程,保存当前寄存器状态
• 通过 symbolication 将地址映射到函数名
• 将调用栈入栈到采样树
• 输出火焰图(Flame Graph)
10. Core Animation 离屏渲染的检测方法?
答:
• Instruments → Core Animation → Offscreen Draws
• 常见原因:圆角、阴影、clip、mask
• 修复:使用 CAShapeLayer + bezierPath
• 避免:masksToBounds、复杂圆角21.2 面试回答模板
> "iOS 调试主要用 Xcode 内置的 LLDB 和 Instruments。
> LLDB 负责断点调试和变量检查,支持条件断点、日志断点、观察断点等。
> Instruments 做性能分析(Time Profiler/Allocations/Leaks/Core Animation)。
> 内存泄漏用 Allocations + Leaks 联合检测,数据竞争用 TSan,内存错误用 ASan。
> 崩溃日志通过 dSYM 文件符号化分析(symbolicatecrash)。
> 对于模拟器调试,Xcode Simulator 提供丰富的模拟功能(弱网/GPS/电量等)。
> 生产环境监控主要依赖 Crashlytics 等第三方工具。"
> "LLDB 和 Instruments 是 iOS 调试的两大核心工具。
> LLDB 是命令行调试器,通过 ptrace + Mach Exception 实现进程控制。
> 断点本质是修改代码的第一条指令为断点指令(x86: INT3 / ARM64: BRK)。
> Instruments 是性能分析工具集,通过 hooks + 采样实现无侵入分析。
> Allocations 通过 hook malloc/free 实现内存分配追踪。
> Leaks 通过标记-清除算法检测内存泄漏。
> Time Profiler 通过采样(SIGPROF 信号)实现 CPU 分析。
> ASan 通过 shadow memory 实现内存错误检测。
> TSan 通过 Happens-Before 关系实现数据竞争检测。"
> "iOS 和 Android 调试工具各有特点:
> iOS:Xcode + LLDB + Instruments 一体化
> Android:Android Studio + Logcat + Profiler + LeakCanary 组合
> iOS 的 dSYM 符号化是崩溃分析的核心
> Android 的 Logcat 是日志分析的核心
> iOS 的 Instruments 功能比 Android Profiler 更丰富
> Android 的 LeakCanary 比 iOS 的 Leaks 更自动化
> 两者都支持 ASan/TSan 检测内存/线程错误"21.3 面试考点矩阵
iOS 调试面试考点矩阵:
┌──────────────┬──────────┬──────────┬──────────┬──────────┬──────────┐
│ 知识点 │ 初级 │ 中级 │ 高级 │ 专家级 │ 面试频率 │
├──────────────┼──────────┼──────────┼──────────┼──────────┼──────────┤
│ LLDB 基本命令 │ ✅ │ ✅ │ ✅ │ ✅ │ ⭐⭐⭐⭐⭐ │
│ 断点类型 │ ✅ │ ✅ │ ✅ │ ✅ │ ⭐⭐⭐⭐⭐ │
│ Instruments │ ✅ │ ✅ │ ✅ │ ✅ │ ⭐⭐⭐⭐ │
│ Allocations │ ✅ │ ✅ | ✅ │ ✅ │ ⭐⭐⭐⭐ │
│ Leaks │ ✅ | ✅ │ ✅ │ ✅ │ ⭐⭐⭐⭐⭐ │
│ Time Profiler │ ❌ │ ✅ │ ✅ │ ✅ │ ⭐⭐⭐⭐ │
│ Zombies │ ❌ │ ✅ │ ✅ │ ✅ │ ⭐⭐⭐ │
│ ASan/TSan | ❌ │ ✅ │ ✅ │ ✅ │ ⭐⭐⭐⭐ │
│ dSYM 符号化 │ ❌ │ ✅ │ ✅ │ ✅ │ ⭐⭐⭐⭐ │
│ Core Anim. │ ❌ │ ✅ │ ✅ │ ✅ │ ⭐⭐⭐ │
│ System Trace │ ❌ │ ❌ │ ✅ │ ✅ │ ⭐⭐ │
│ LLDB 脚本 │ ❌ │ ❌ │ ✅ │ ✅ │ ⭐⭐ │
│ Sanitizer 原理 │ ❌ │ ❌ │ ✅ │ ✅ │ ⭐⭐⭐ │
│ 崩溃分析 │ ❌ │ ✅ │ ✅ │ ✅ │ ⭐⭐⭐⭐ │
│ iOS vs Android │ ❌ │ ❌ | ✅ │ ✅ │ ⭐⭐⭐ │
└──────────────┴──────────┴──────────┴──────────┴──────────┴──────────┘
面试准备建议:
• 初级:掌握 LLDB 基本命令 + 断点使用
• 中级:熟练使用 Instruments + Leaks + Allocations
• 高级:理解 ASan/TSan 原理 + 崩溃符号化
• 专家:LLDB 脚本扩展 + System Trace + 底层原理附录
A. LLDB 命令速查表
LLDB 命令速查:
┌──────┬───────────────────────┬──────────────────────────────┐
│ 类别 │ 命令 │ 说明 │
├──────┼───────────────────────┼──────────────────────────────┤
│ 断点 │ breakpoint list │ 列出所有断点 │
│ │ breakpoint delete N │ 删除断点 N │
│ │ breakpoint set -n fn │ 按函数名设置断点 │
│ │ breakpoint set -f f -l l │ 按文件+行号设置断点 │
│ │ breakpoint enable N │ 启用断点 N │
│ │ breakpoint disable N │ 禁用断点 N │
│ │ breakpoint ignore N c │ 断点 N 忽略 c 次 │
│ │ breakpoint command add │ 添加断点命令 │
│ │ │ N │
│ 执行 │ continue │ 继续执行 │
│ │ step │ 单步进入函数 │
│ │ next │ 单步跳过函数 │
│ │ finish │ 执行到当前函数返回 │
│ │ jump to line N │ 跳转到行 N │
│ │ thread return expr │ 直接返回(修改变量) │
│ 变量 │ po expr │ 打印对象 │
│ │ p expr │ 打印值 │
│ │ expr -- expr │ 求表达式 │
│ │ frame variable │ 查看帧变量 │
│ │ register read │ 查看寄存器 │
│ 内存 │ memory read addr │ 读取内存 │
│ │ memory write addr val │ 写入内存 │
│ │ memscan addr sz val │ 内存扫描 │
│ │ heap dump │ 堆转储 │
│ │ heap query Type │ 堆查询 │
│ 线程 │ thread list │ 列出线程 │
│ │ thread select N │ 选择线程 N │
│ │ thread backtrace │ 线程 backtrace │
│ │ thread backtrace all │ 所有线程 backtrace │
│ 模块 │ image list │ 列出镜像 │
│ │ image lookup -a addr │ 查找地址 │
│ │ module list │ 列出模块 │
│ 汇编 │ disassemble │ 反汇编 │
│ │ disassemble -n fn │ 反汇编函数 │
│ 脚本 │ command alias a b │ 命令别名 │
│ │ script import file.py │ 导入 Python 脚本 │
│ │ settings set x y │ 设置选项 │
│ 观察 │ watchpoint set var │ 设置观察点 │
│ │ watchpoint list │ 列出观察点 │
│ │ watchpoint delete N │ 删除观察点 N │
└──────┴───────────────────────┴──────────────────────────────┘B. Instruments 工具速查表
Instruments 工具速查:
┌───────────────────┬──────────────────────────────┐
│ 工具 │ 用途 │
├───────────────────┼──────────────────────────────┤
│ Time Profiler │ CPU 性能分析(采样) │
│ Allocations │ 内存分配追踪 │
│ Leaks │ 内存泄漏检测 │
│ Zombies │ 悬垂指针检测 │
│ Core Animation │ 渲染性能分析 │
│ Network │ 网络请求分析 │
│ Energy Log │ 能耗分析 │
│ System Trace │ 系统级追踪 │
│ File Activity │ 文件操作分析 │
│ OpenGL ES │ GPU 渲染分析 │
│ Metal Shader │ Metal 着色器分析 │
│ Thread Activator │ 线程创建/销毁 │
│ Activity Monitor │ 进程活动监控 │
│ VM Tracker │ 虚拟内存追踪 │
│ ObjectAlloc │ Objective-C 对象追踪 │
│ API Log │ API 调用日志 │
│ GPU Debugger │ GPU 调试 │
│ Metal System Trace│ Metal 系统追踪 │
│ Disk Activity │ 磁盘活动分析 │
│ Network Link Cond │ 网络链接条件器 │
│ Time Profiler + │ CPU+GPU 联合分析 │
│ GPU Profiler │ │
└───────────────────┴──────────────────────────────┘C. 常见崩溃码速查表
崩溃码速查:
┌──────────────────────┬──────────────────────────────┐
│ 崩溃码 │ 含义 │
├──────────────────────┼──────────────────────────────┤
│ EXC_BAD_ACCESS │ 内存访问错误(野指针) │
│ EXC_CRASH │ 进程崩溃 │
│ EXC_ARITHMETIC │ 算术错误(除零/溢出) │
│ EXC_BREAKPOINT │ 断点/断言失败 │
│ SIGABRT │ 进程中止(异常未捕获) │
│ SIGSEGV │ 段错误(非法内存访问) │
│ SIGBUS │ 总线错误(对齐错误) │
│ SIGILL │ 非法指令 │
│ SIGFPE │ 浮点异常 │
│ SIGPIPE │ 管道破裂 │
│ SIGKILL │ 强制终止(内存不足/超时) │
│ MACH_SEND_INVALID │ Mach 消息发送错误 │
│ KERN_PROTECTION │ 内存保护失败 │
│ KERN_INVALID_ADDRESS │ 无效地址 │
│ EXC_ARM_DA_ALIGN │ ARM 对齐异常 │
└──────────────────────┴──────────────────────────────┘D. Xcode 调试快捷键速查
Xcode 调试快捷键:
┌───────────────────────────┬──────────────────────────────┐
│ 快捷键 │ 功能 │
├───────────────────────────┼──────────────────────────────┤
│ ⌘ + R │ 运行/调试 │
│ ⌘ + I │ Profile (Instruments) │
│ ⌘ + Shift + L │ 导航列表 │
│ ⌘ + Shift + C │ 控制台 │
│ ⌘ + 5 │ Debug Navigator │
│ ⌘ + 6 │ Variables (变量观察器) │
│ ⌘ + 7 │ Memory (内存视图) │
│ ⌘ + 8 │ Threads (线程面板) │
│ ⌘ + 9 │ Console (控制台) │
│ ⌘ + Shift + 2 │ Devices and Simulators │
│ F5 │ Step Over │
│ F6 │ Step Into │
│ F7 │ Step Over (Skip Function) │
│ F8 │ Step Out │
│ ⌘ + Y │ 条件断点 │
│ ⌘ + ⌥ + Y │ 日志断点 │
│ ⌘ + ⌥ + Enter │ 折叠/展开代码 │
│ ⌘ + ⌥ + ↑/↓ │ 移动行 │
│ ⌘ + Click │ 跳转到定义 │
│ ⌘ + ⇧ + Click │ 跳转到声明 │
│ ⌘ + [ / ⌘ + ] │ 折叠/展开 │
│ ⌘ + Option + [ / ] │ 展开/折叠 │
└───────────────────────────┴──────────────────────────────┘本文档对标 Android 26_Xcode_Debug 的深度,涵盖 iOS 调试全栈知识体系