Appearance
01 - 内存管理深度
目录
1. MRC vs ARC 机制深度
ARC vs MRC 深度分析:
┌─────────────────────────────────────────────────────────────────────────────┐
│ │
│ ARC 的核心原理: │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ ARC(Automatic Reference Counting)自动引用计数 │
│ │ • 编译器在编译期自动插入 retain/release 调用 │
│ │ • 每个对象维护一个引用计数器 │
│ │ • 引用计数归零时自动释放内存 │
│ │ • 线程安全的(原子操作) │
│ │ │
│ │ ARC 的引用计数操作: │
│ │ ┌────────────────────────────────────────────────────────┐ │
│ │ │ 方法 │ 作用 │
│ │ ├─────────────────────────┼───────────────────────────────┤ │
│ │ │ retain() │ 引用计数 +1 │
│ │ │ release() │ 引用计数 -1,归零则释放 │
│ │ │ retainCount() │ 获取当前引用计数 │
│ │ │ autorelease() │ 延迟释放(加入自动释放池) │
│ │ │ autoreleasePool() │ 创建自动释放池 │
│ │ └──────────────────────┴─────────────────────────────────┘ │
│ │ │
│ │ ARC 编译期自动插入的代码: │
│ │ class MyClass: NSObject { │
│ │ var name: String │
│ │ init(name: String) { │
│ │ self.name = name │
│ │ } │
│ │ } │
│ │ │
│ │ // ARC 编译后自动变为: │
│ │ class MyClass: NSObject { │
│ │ var name: String │
│ │ init(name: String) { │
│ │ self.name = name.retain() // 编译器自动插入 │
│ │ } │
│ │ } │
│ │ │
│ │ 自动释放池(Autorelease Pool): │
│ │ ┌───────────────────────────────────────────────────────┐ │
│ │ │ 作用: │
│ │ │ • 延迟释放对象 │
│ │ │ • 避免在循环中频繁创建/释放对象 │
│ │ │ • iOS 主循环自动管理 │
│ │ │ │
│ │ │ // 手动创建自动释放池 │
│ │ │ autoreleasepool { │
│ │ │ for _ in 0..<10000 { │
│ │ │ let temp = NSMutableString() // 自动加入池 │
│ │ │ temp.append("test") │
│ │ │ } │
│ │ │ } // 自动释放所有对象 │
│ │ │ │
│ │ │ iOS 主循环自动管理: │
│ │ │ • 每次 RunLoop 迭代自动创建/释放 │
│ │ │ • 子线程需手动管理 │
│ │ │ • 异步任务需手动管理 │
│ │ └───────────────────────────────────────────────────────┘ │
│ │ │
│ │ ARC 的生命周期管理: │
│ │ ┌─────────────────────────────────────────────────────────────┐ │
│ │ │ 对象创建时: │
│ │ │ 1. 分配内存(alloc) │
│ │ │ 2. 初始化(init) │
│ │ │ 3. 引用计数 = 1 │
│ │ │ │
│ │ │ 对象引用时: │
│ │ │ 1. 赋值 → retain (+1) │
│ │ │ 2. 传递 → retain (+1) │
│ │ │ │
│ │ │ 对象释放时: │
│ │ │ 1. 超出作用域 → release (-1) │
│ │ │ 2. 赋值 nil → release (-1) │
│ │ │ 3. 引用计数 = 0 → 调用 dealloc → 释放内存 │
│ │ │ │
│ │ │ 对象池化(Autorelease): │
│ │ │ 1. 添加到自动释放池 │
│ │ │ 2. 池释放时统一 release │
│ │ │ 3. 避免频繁内存分配/释放 │
│ │ └─────────────────────────────────────────────────────────────┘ │
│ │ │
│ │ MRC(Manual Reference Counting)手动引用计数: │
│ │ ┌─────────────────────────────────────────────────────────────┐ │
│ │ │ • 手动调用 retain/release │
│ │ │ • iOS 5 之前使用 │
│ │ │ • 易出错(泄漏/野指针/双重释放) │
│ │ │ • 已废弃,现代 iOS 开发不使用 │
│ │ │ │
│ │ │ MRC vs ARC 对比: │
│ │ │ ┌──────────────────────┬───────────────────────────────┐ │
│ │ │ │ 特性 │ MRC │ ARC │ │
│ │ │ ├────────────┼───────────────┬──────┼──────────────────┤ │
│ │ │ │ 引用计数 │ 手动 │ 自动 │ │ │
│ │ │ │ 内存泄漏 │ 高频 │ 低频 │ │ │
│ │ │ │ 开发效率 │ 低 │ 高 │ │ │
│ │ │ │ 性能 │ 可能更高 │ 略低 │ │ │
│ │ │ │ 推荐 │ 不推荐 │ 唯一选择 │ │ │
│ │ └───────────┴──────────────────┴───────────────────────┘ │
│ │ │
│ │ ⚠️ ARC 的例外情况: │
│ │ • __unsafe_unretained — 不安全无主引用(已废弃) │
│ │ • __weak — 弱引用(不持有) │
│ │ • __autoreleasing — 自动释放参数 │
│ │ • UnsafeMutablePointer — 手动内存管理 │
│ │ • C/C++ 代码 — 手动管理 │
│ │ • NSAllocateMemoryPages — 手动分配 │
│ │ │
│ │ ARC 与 Objective-C 桥接: │
│ │ • Objective-C 对象使用 ARC │
│ │ • Swift 对象使用 ARC │
│ │ • Mixed Swift/Objective-C 混编:两者都使用 ARC │
│ │ • C 指针:手动管理( UnsafePointer, UnsafeMutablePointer) │
│ │ │
│ │ 性能分析: │
│ │ • retain/release:约 10-30ns(原子操作) │
│ │ • autorelease:约 50-100ns(加入自动释放池) │
│ │ • dealloc:约 100-500ns(释放内存 + 清理) │
│ │ • ARC 在编译期优化,性能接近手动管理 │
│ └─────────────────────────────────────────────────────────────┘ │
│ │ │
│ │ ARC 的限制: │
│ │ • 无法管理循环引用(需手动处理) │
│ │ • 无法管理 C 指针(需手动) │
│ │ • 无法管理 NSAllocateMemoryPages 分配 │
│ │ • 无法管理 CoreFoundation 对象(需桥接) │
│ │ • 无法管理 unowned 对象的内存 │
│ │ │
│ │ 总结: │
│ │ • ARC 是现代 iOS 开发唯一选择 │
│ │ • 循环引用需手动处理(weak/unowned) │
│ │ • C 指针需手动管理 │
│ │ • 自动释放池在异步/循环场景中手动管理 │
│ └─────────────────────────────────────────────────────────────┘ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │
└────────────────────────────────────────────────────────────────────────────┘
*/2. 引用计数原理与实现
引用计数原理深度分析:
┌─────────────────────────────────────────────────────────────────────────────┐
│ │
│ 引用计数的底层实现: │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ 引用计数存储在 isa 指针中: │
│ │ ┌──────────────────────────────────────────────────────┐ │
│ │ │ ┌─────────────────────────────────────────────────┐ │ │
│ │ │ │ isa 指针(8 字节) │ │ │
│ │ │ │ ┌──────┬───────┬───────────┬──────────────┐ │ │
│ │ │ │ │ isa │ class │ hasAux │ refCount │ │ │
│ │ │ │ │ (3) │ (24) │ (1) │ (32) │ │ │
│ │ │ │ └──────┴───────┴───────────┴──────────────┘ │ │
│ │ │ │ │ │
│ │ │ │ refCount 的存储: │
│ │ │ │ • Small refcount:引用计数 ≤ 1023,存储在 isa 中 │
│ │ │ │ • Large refcount:引用计数 > 1023,存储在额外堆块中 │
│ │ │ │ • refcount > 1024:分配到额外堆块 │
│ │ │ └──────────────────────────────────────────────────────┘ │
│ │ │
│ │ 引用计数的操作: │
│ │ ┌─────────────────────────────────────────────────────────────┐ │
│ │ │ // retain 操作: │
│ │ │ - (instancetype)retain { │
│ │ │ if (refCount > 0 && refCount < 1023) { │
│ │ │ refCount += 1; // 原子操作 │
│ │ │ } else { │
│ │ │ // 使用锁 + CAS(compare-and-swap) │
│ │ │ // 或分配额外 refcount 块 │
│ │ │ } │
│ │ │ return self; │
│ │ │ } │
│ │ │ │
│ │ │ // release 操作: │
│ │ │ - (oneway void)release { │
│ │ │ if (refCount > 0) { │
│ │ │ refCount -= 1; // 原子操作 │
│ │ │ if (refCount == 0) { │
│ │ │ dealloc(); // 释放内存 │
│ │ │ } │
│ │ │ } │
│ │ │ } │
│ │ └─────────────────────────────────────────────────────────────┘ │
│ │ │
│ │ 引用计数的优化: │
│ │ ┌─────────────────────────────────────────────────────────────┐ │
│ │ │ • 小引用计数(≤ 1023):直接修改 isa 中的 refCount │
│ │ │ • 大引用计数(> 1023):使用锁 + CAS 原子操作 │
│ │ │ • 指针压缩:64 位地址只使用 42 位,减少内存占用 │
│ │ │ • ARC 编译期优化:消除冗余 retain/release │
│ │ │ • Retain Pool:批量处理自动释放 │
│ │ └─────────────────────────────────────────────────────────────┘ │
│ │ │
│ │ 引用计数的线程安全: │
│ │ ┌─────────────────────────────────────────────────────────────┐ │
│ │ │ • retain/release 是原子操作(原子指令) │
│ │ │ • iOS 使用 CAS(compare-and-swap)实现 │
│ │ │ • 高并发场景:使用锁保护(pthread_mutex) │
│ │ │ • 原子性保证:引用计数操作不可被中断 │
│ │ │ │
│ │ │ 性能分析: │
│ │ │ • retain/release:约 10-30ns(原子指令) │
│ │ │ • CAS 操作:约 50-100ns │
│ │ │ • 锁保护:约 200-500ns │
│ │ │ • ARC 在编译期优化,性能接近手动管理 │
│ │ └─────────────────────────────────────────────────────────────┘ │
│ │ │
│ │ 内存管理生命周期: │
│ │ ┌─────────────────────────────────────────────────────────────┐ │
│ │ │ 1. 对象创建 → 分配内存,引用计数 = 1 │
│ │ │ 2. 对象引用 → retain (+1) │
│ │ │ 3. 对象传递 → retain (+1) │
│ │ │ 4. 超出作用域 → release (-1) │
│ │ │ 5. 引用计数归零 → dealloc → 释放内存 │
│ │ │ │
│ │ │ 生命周期图示: │
│ │ │ ┌─────────────────────────────────────────────────────┐ │
│ │ │ │ alloc/init → retain → retain → release → release │ │
│ │ │ │ refCount: 1 → 2 → 3 → 2 → 1 → 0 (dealloc) │ │
│ │ │ └─────────────────────────────────────────────────────┘ │
│ │ └─────────────────────────────────────────────────────────────┘ │
│ │ │
│ │ Swift 与 Objective-C 引用计数对比: │
│ │ ┌─────────────────────────────────────────────────────────────┐ │
│ │ │ 特性 │ Swift ARC │ Objective-C ARC │ │
│ │ ├──────────────┼─────────────────────┬───────────────────┤ │
│ │ │ 引用计数 │ 编译器自动插入 │ 编译器自动插入 │ │
│ │ │ 线程安全 │ 原子操作 │ 原子操作 │ │
│ │ │ 循环引用 │ weak 自动处理 │ weak 自动处理 │ │
│ │ │ unowned │ Swift 特有 │ 无 │ │
│ │ │ weak │ Swift + ObjC │ Swift + ObjC │ │
│ │ │ ARC 实现 │ Swift 编译器 + LLVM │ Clang + LLVM │ │
│ │ └──────────────┴─────────────────────┴───────────────────┘ │
│ │ │
│ │ 总结: │
│ │ • ARC 是现代 iOS 开发的唯一选择 │
│ │ • 引用计数在编译期自动管理 │
│ │ • 循环引用需手动处理(weak/unowned) │
│ │ • 引用计数操作是原子操作,线程安全 │
│ │ • C 指针需手动管理(UnsafePointer/UnsafeMutablePointer) │
│ │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ └───────────────────────────────────────────────────────────────┘ │
│ │
└────────────────────────────────────────────────────────────────────────────┘
*/3. 循环引用分析
循环引用深度分析:
┌─────────────────────────────────────────────────────────────────────────────┐
│ 循环引用的本质: │
│ ┌────────────────────────────────────────────────────────────┐ │
│ │ • 两个或多个对象相互持有对方的强引用 │
│ │ • 引用计数永远不为零,对象永远无法释放 │
│ │ • 内存泄漏 │
│ │ • 常见的循环引用场景: │
│ │ 1. 对象 A 持有对象 B,对象 B 持有对象 A │
│ │ 2. 闭包捕获 self(强引用) │
│ │ 3. delegate 持有 self(强引用) │
│ │ 4. Timer 持有 self(强引用) │
│ │ 5. Notification 观察者持有 self(强引用) │
│ │ │ │ │
│ │ │ 循环引用的检测: │
│ │ │ • Instruments(Allocations / Leaks) │
│ │ │ • Address Sanitizer(ASan) │
│ │ │ • 手动检查循环引用 │
│ │ │ │
│ │ │ 循环引用的修复: │
│ │ │ • 使用 weak 修饰符(不持有对方) │
│ │ │ • 使用 unowned 修饰符(已知不释放) │
│ │ │ • 使用 [weak self] 闭包捕获(不持有 self) │
│ │ │ • 使用 [unowned self] 闭包捕获(已知不释放) │
│ │ └────────────────────────────────────────────────────────────┘ │
│ │ │
│ │ 循环引用示例: │
│ │ ┌─────────────────────────────────────────────────────────────┐ │
│ │ │ // 错误的循环引用 │
│ │ │ class ViewController { │
│ │ │ var viewModel: ViewModel? // strong 持有 │
│ │ │ } │
│ │ │ │
│ │ │ class ViewModel { │
│ │ │ var viewController: ViewController? // strong 持有 ❌ │
│ │ │ } │
│ │ │ │
│ │ │ // 正确的循环引用(weak) │
│ │ │ class ViewController { │
│ │ │ var viewModel: ViewModel? │
│ │ │ } │
│ │ │ │
│ │ │ class ViewModel { │
│ │ │ weak var viewController: ViewController? // weak ✅ │
│ │ │ } │
│ │ │ │
│ │ │ 常见的循环引用场景: │
│ │ │ 1. delegate(weak) │
│ │ │ 2. timer(weak) │
│ │ │ 3. notification(removeObserver) │
│ │ │ 4. 闭包([weak self] / [unowned self]) │
│ │ │ 5. timer(weak) │
│ │ │ 6. timer(weak) │
│ │ └─────────────────────────────────────────────────────────────┘ │
│ │ │
│ │ 总结: │
│ │ • 循环引用是 iOS 开发最常见的内存泄漏原因 │
│ │ • 使用 weak/unowned 修复循环引用 │
│ │ • 使用 Instruments 检测内存泄漏 │
│ │ • 使用 Address Sanitizer 检测循环引用 │
│ └─────────────────────────────────────────────────────────────┘ │
│ └───────────────────────────────────────────────────────────────┘ │
│ │
│ 循环引用修复: │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ // 闭包循环引用修复 │
│ │ // ❌ 错误:闭包强引用 self │
│ │ class MyViewController: UIViewController { │
│ │ var task: Task<Void, Never>? │
│ │ func start() { │
│ │ task = Task { │
│ │ await someAsync() // 强引用 self ❌ │
│ │ } │
│ │ } │
│ │ } │
│ │ │
│ │ // ✅ 正确:使用 weak self │
│ │ class MyViewController: UIViewController { │
│ │ var task: Task<Void, Never>? │
│ │ func start() { │
│ │ task = Task { │
│ │ await self?.doWork() // weak self ✅ │
│ │ } │
│ │ } │
│ │ } │
│ │ │
│ │ // ✅ 正确:使用 unowned self(已知不释放) │
│ │ class MyViewController: UIViewController { │
│ │ var task: Task<Void, Never>? │
│ │ func start() { │
│ │ task = Task { │
│ │ let value = await someAsync() │
│ │ await MainActor.run { [unowned self] in │
│ │ self.updateUI(value) │
│ │ } │
│ │ } │
│ │ } │
│ │ } │
│ │ │
│ │ // ✅ 正确:使用 [weak self] 闭包捕获 │
│ │ class MyViewController: UIViewController { │
│ │ func start() { │
│ │ someAsync { [weak self] in │
│ │ guard let self = self else { return } │
│ │ self.updateUI() │
│ │ } │
│ │ } │
│ │ } │
│ │ │
│ │ // ✅ 正确:使用 [unowned self] 闭包捕获 │
│ │ class MyViewController: UIViewController { │
│ │ func start() { │
│ │ someAsync { [unowned self] in │
│ │ self.updateUI() // 不检查 nil │
│ │ } │
│ │ } │
│ │ } │
│ │ │
│ │ ⚠️ 闭包捕获: │
│ │ • [weak self] — 捕获为 optional │
│ │ • [unowned self] — 捕获为非 optional │
│ │ • [unowned unsafe self] — 不持有(可能野指针) │
│ │ • [strong self] — 强制强捕获(不推荐) │
│ │ │
│ │ 总结: │
│ │ • 使用 weak 捕获 optional(安全) │
│ │ • 使用 unowned 捕获非 optional(已知不释放) │
│ │ • 使用 [weak self] 闭包捕获(推荐) │
│ │ • 使用 [unowned self] 闭包捕获(已知不释放) │
│ │ • 使用 [strong self] 闭包捕获(不推荐) │
│ │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │ │ │
│ │ │ 总结: │
│ │ │ • 使用 weak 捕获 optional(安全) │
│ │ │ • 使用 unowned 捕获非 optional(已知不释放) │
│ │ │ • 使用 [weak self] 闭包捕获(推荐) │
│ │ │ • 使用 [unowned self] 闭包捕获(已知不释放) │
│ │ │ • 使用 [strong self] 闭包捕获(不推荐) │
│ │ └─────────────────────────────────────────────────────────────┘ │
│ └───────────────────────────────────────────────────────────────┘ │
│ │
└────────────────────────────────────────────────────────────────────────────┘
*/4. Weak/Unowned/Weakly Captured
Weak/Unowned/Weakly Captured 深度分析:
┌─────────────────────────────────────────────────────────────────────────────┐
│ Weak vs Unowned vs 闭包捕获: │
│ ┌────────────────────────────────────────────────────────────┐ │
│ │ Weak 修饰符: │
│ │ • 引用计数 -1(不持有对方) │
│ │ • 对象释放后为 nil │
│ │ • 安全:自动设为 nil │
│ │ • 推荐:默认使用 weak │
│ │ │
│ │ Unowned 修饰符: │
│ │ • 不持有对方,但对象释放后不设为 nil │
│ │ • 不安全:访问已释放对象会崩溃 │
│ │ • 适用:已知对方一定不释放 │
│ │ │
│ │ Weakly Captured(闭包捕获): │
│ │ • [weak self] — optional 捕获 │
│ │ • [unowned self] — 非 optional 捕获 │
│ │ • [strong self] — 强制强捕获 │
│ │ │
│ │ Weak vs Unowned 对比: │
│ │ ┌───────┬──────────────────┬───────┬──────────┬──────────┐ │
│ │ │ 特性 │ Weak │ Unowned │ 安全性 │ 适用场景 │ │
│ │ ├───────┼──────────────────┼───────────┼──────────┼─────────────┤ │
│ │ │ 内存 │ -1 (不持有) │ -1 (不持有) │ 安全 │ 默认使用 │ │
│ │ │ 对象释放 │ 自动设为 nil │ 不检查 │ 不安全 │ 已知不释放 │ │
│ │ │ 线程安全 │ ✅ 线程安全 │ ✅ 线程安全 │ ✅ 安全 │ 默认使用 │ │
│ │ │ 适用 │ 默认使用 │ 已知不释放 │ ⚠️ 不安全 │ 已知不释放 │ │
│ │ └───────┴──────────────────┴───────────┴──────────┴─────────────┘ │
│ │ │
│ │ 闭包捕获: │
│ │ ┌───────┬───────────────────┬───────────────┬───────────┐ │
│ │ │ 捕获 │ [weak self] │ [unowned self] │ [strong self] │ │
│ │ ├───────┼───────────────────┼───────────────┼───────────┤ │
│ │ │ 捕获类型 │ optional │ 非 optional │ 强捕获 │ │
│ │ │ 对象释放 │ 可选绑定 │ 不检查 │ 不检查 │ │
│ │ │ 安全性 │ ✅ 安全 │ ⚠️ 不安全 │ ✅ 安全 │ │
│ │ │ 适用 │ 默认使用 │ 已知不释放 │ 已知不释放 │ │
│ │ └───────┴───────────────────┴───────────────┴───────────┘ │
│ └────────────────────────────────────────────────────────────┘ │
│ │
│ 闭包捕获总结: │
│ ┌───────┬───────────────────────────────────────────────────────┐ │
│ │ 捕获 │ [weak self] │ [unowned self] │ │
│ │ ├───────┼─────────────────────────┼───────────────────┤ │
│ │ │ 捕获 │ optional 捕获 │ 非 optional 捕获 │ │
│ │ │ 对象释放 │ 自动设为 nil │ 不检查 │ │
│ │ │ 安全性 │ ✅ 安全 │ ⚠️ 不安全 │ │
│ │ │ 适用 │ 默认使用 │ 已知不释放 │ │
│ │ └───────┴─────────────────────────┴───────────────────┘ │
│ └───────────────────────────────────────────────────────────────┘ │
│ │
│ 总结: │
│ • 使用 weak 捕获 optional(安全) │
│ • 使用 unowned 捕获非 optional(已知不释放) │
│ • 使用 [weak self] 闭包捕获(推荐) │
│ • 使用 [unowned self] 闭包捕获(已知不释放) │
│ • 使用 [strong self] 闭包捕获(不推荐) │
│ │
└────────────────────────────────────────────────────────────────────────────┘
*/5. 内存布局与优化
内存布局与优化深度分析:
┌─────────────────────────────────────────────────────────────────────────────┐
│ iOS 内存布局: │
│ ┌────────────────────────────────────────────────────────────┐ │
│ │ 内存分区: │
│ │ ┌─────────────────────────────────────────────────────┐ │
│ │ │ ┌────────────────────────────────────────────────┐ │ │
│ │ │ │ Stack(栈) — 局部变量、函数参数 │ │ │
│ │ │ │ • 自动管理(push/pop) │ │ │
│ │ │ │ • 容量有限(默认 1-8MB) │ │ │
│ │ │ │ • LIFO 结构 │ │ │
│ │ │ └────────────────────────────────────────────────┘ │ │
│ │ │ ┌────────────────────────────────────────────────┐ │ │
│ │ │ │ Heap(堆) — 动态分配(alloc/init) │ │ │
│ │ │ │ • 手动管理(retain/release) │ │ │
│ │ │ │ • 容量大(可用内存) │ │ │
│ │ │ │ • 碎片化 │ │ │
│ │ │ └────────────────────────────────────────────────┘ │ │
│ │ │ ┌────────────────────────────────────────────────┐ │ │
│ │ │ │ Data(数据段) — 全局变量、常量 │ │ │
│ │ │ │ • 初始化后不变 │ │ │
│ │ │ │ • 容量有限 │ │ │
│ │ │ └────────────────────────────────────────────────┘ │ │
│ │ │ ┌────────────────────────────────────────────────┐ │ │
│ │ │ │ Text(代码段) — 程序代码 │ │ │
│ │ │ │ • 只读 │ │ │
│ │ │ │ • 容量有限 │ │ │
│ │ │ └────────────────────────────────────────────────┘ │ │
│ │ └─────────────────────────────────────────────────────┘ │
│ │ │
│ │ 内存优化策略: │
│ │ ┌─────────────────────────────────────────────────────────────┐ │
│ │ │ • 减少对象创建(对象池、重用) │
│ │ │ • 减少内存分配(预分配、批量操作) │
│ │ │ • 减少内存碎片(紧凑存储、对齐) │
│ │ │ • 减少内存占用(使用更小的类型、压缩) │
│ │ │ • 减少内存泄漏(weak、unowned、闭包捕获) │
│ │ │ • 减少内存峰值(异步处理、分批处理) │
│ │ └─────────────────────────────────────────────────────────────┘ │
│ │ │
│ │ 内存优化技巧: │
│ │ ┌─────────────────────────────────────────────────────────────┐ │
│ │ │ // 减少内存分配 │
│ │ │ // ❌ 错误:循环中频繁创建数组 │
│ │ │ var result: [String] = [] │
│ │ │ for _ in 0..<10000 { │
│ │ │ result.append(String(format: "item_%d", i)) │
│ │ │ } │
│ │ │ │
│ │ │ // ✅ 正确:预分配容量 │
│ │ │ var result = [String](repeating: "", count: 10000) │
│ │ │ for i in 0..<10000 { │
│ │ │ result[i] = String(format: "item_%d", i) │
│ │ │ } │
│ │ │ │
│ │ │ // ✅ 正确:使用 StringBuilder( NSMutableString) │
│ │ │ var builder = NSMutableString() │
│ │ │ for i in 0..<10000 { │
│ │ │ builder.append("item_\(i)") │
│ │ │ } │
│ │ │ │
│ │ │ // 内存泄漏检测: │
│ │ │ // • Instruments(Allocations / Leaks) │
│ │ │ // • Address Sanitizer(ASan) │
│ │ │ // • 手动检查循环引用 │
│ │ └─────────────────────────────────────────────────────────────┘ │
│ │ │
│ │ 内存布局总结: │
│ │ • Stack:局部变量,自动管理 │
│ │ • Heap:动态分配,手动管理(ARC) │
│ │ • Data:全局变量,初始化后不变 │
│ │ • Text:程序代码,只读 │
│ │ • 优化:减少对象创建、减少内存分配、减少内存碎片 │
│ │ • 泄漏:使用 Instruments 和 Address Sanitizer 检测 │
│ │ │
│ └───────────────────────────────────────────────────────────────┘ │
│ └───────────────────────────────────────────────────────────────┘ │
│ │
│ 内存管理策略总结: │
│ ┌───────┬─────────────────┬───────────────┬──────────┬─────────────┐ │
│ │ 策略 │ 原理 │ 适用场景 │ 安全性 │ 推荐 │ │
│ │ ├───────┼────────────────────┼───────────────┼──────────┼─────────────┤ │
│ │ Weak │ 不持有对方 │ 默认使用 │ ✅ 安全 │ 默认使用 │ │
│ │ Unowned │ 不持有,不检查 │ 已知不释放 │ ⚠️ 不安全 │ 已知不释放 │ │
│ │ Weakly │ optional 捕获 │ 默认使用 │ ✅ 安全 │ 默认使用 │ │
│ │ Unownedly │ 非 optional 捕获 │ 已知不释放 │ ⚠️ 不安全 │ 已知不释放 │ │
│ │ Strongly │ 强捕获 │ 已知不释放 │ ✅ 安全 │ 已知不释放 │ │
│ └───────┴────────────────────┴───────────────┴──────────┴─────────────┘ │
│ │
│ 总结: │
│ • 使用 weak 捕获 optional(安全) │
│ • 使用 unowned 捕获非 optional(已知不释放) │
│ • 使用 [weak self] 闭包捕获(推荐) │
│ • 使用 [unowned self] 闭包捕获(已知不释放) │
│ • 使用 [strong self] 闭包捕获(不推荐) │
│ │
└────────────────────────────────────────────────────────────────────────────┘
*/6. 内存泄漏检测与修复
内存泄漏检测与修复深度分析:
┌─────────────────────────────────────────────────────────────────────────────┐
│ 内存泄漏检测工具: │
│ ┌────────────────────────────────────────────────────────────┐ │
│ │ • Instruments(Allocations / Leaks) │
│ │ • Address Sanitizer(ASan) │
│ │ • 手动检查循环引用 │
│ │ • 内存压力通知(UIApplication.didReceiveMemoryWarningNotification) │
│ │ • 自动释放池检查 │
│ │ │
│ │ 内存泄漏检测流程: │
│ │ ┌────────────────────────────────────────────────────────┐ │
│ │ │ 1. 打开 Instruments → Allocations │
│ │ │ 2. 记录分配点(Record Allocation Points) │
│ │ │ 3. 模拟用户操作 │
│ │ │ 4. 观察泄漏对象 │
│ │ │ 5. 检查引用链(Reference Counters) │
│ │ │ 6. 修复循环引用 │
│ │ │ │
│ │ │ 内存泄漏修复: │
│ │ │ • 使用 weak 修饰 delegate │
│ │ │ • 使用 weak/unowned 闭包捕获 │
│ │ │ • 使用 [weak self] 闭包捕获 │
│ │ │ • 使用 [unowned self] 闭包捕获 │
│ │ │ • 移除 Notification 观察者 │
│ │ │ • 停止 Timer │
│ │ │ • 取消 Task │
│ │ │ • 清理资源(图片、数据) │
│ │ └────────────────────────────────────────────────────────┘ │
│ │ │
│ │ 内存泄漏检测工具对比: │
│ │ ┌────────────┬──────────────┬───────────────┬──────────────┐ │
│ │ │ 工具 │ 检测类型 │ 适用场景 │ 推荐 │ │
│ │ ├───────────┼────────────────┼───────────────┼─────────────┤ │
│ │ │ Instruments │ 实时内存分配 │ 生产环境 │ ✅ 首选 │ │
│ │ │ ASan │ 内存错误检测 │ 开发阶段 │ ✅ 开发首选 │ │
│ │ │ 手动检查 │ 循环引用 │ 代码审查 │ ✅ 日常 │ │
│ │ │ 内存压力 │ 内存不足 │ 生产环境 │ ✅ 监控 │ │
│ │ └───────────┴────────────────┴───────────────┴─────────────┘ │
│ │ │
│ │ 内存泄漏修复: │
│ │ ┌────────────────────────────────────────────────────────┐ │
│ │ │ • 使用 weak 修饰 delegate │
│ │ │ • 使用 weak/unowned 闭包捕获 │
│ │ │ • 使用 [weak self] 闭包捕获 │
│ │ │ • 使用 [unowned self] 闭包捕获 │
│ │ │ • 移除 Notification 观察者 │
│ │ │ • 停止 Timer │
│ │ │ • 取消 Task │
│ │ │ • 清理资源(图片、数据) │
│ │ └────────────────────────────────────────────────────────┘ │
│ │ │
│ │ 内存泄漏检测总结: │
│ │ • 使用 Instruments 实时检测内存分配 │
│ │ • 使用 ASan 检测内存错误 │
│ │ • 手动检查循环引用 │
│ │ • 使用内存压力通知监控 │
│ │ • 修复循环引用、清理资源、停止 Timer │
│ │ │
└────────────────────────────────────
---
## 2.5 引用计数底层原理深度分析
### 2.5.1 refcnt 在 isa 中的存储机制refcnt 在 isa 中的存储机制(iOS 17 ARM64): ┌─────────────────────────────────────────────────────────────────────┐ │ objc_object 的 isa 指针结构(64-bit ARM64): │ │ ┌─────────────────────────────────────────────────────────────────┐ │ │ ┌────────────────────────────────────────────────────────────┐ │ │ │ │ 64-bit isa 指针 (8 bytes) │ │ │ │ │ ┌─────┬───────┬───────┬───────┬───────┬───────┬───────┬──┐ │ │ │ │ │0-15 │16-31 │32-39 │40-41 │42 │43 │44-54 │55│ │ │ │ │ │class│flags │bits │extra │has_assoc │has_cxx_dtor │shiftHash│lock│ │ │ │ │ └─────┴───────┴───────┴───────┴───────┴───────┴───────┴──┘ │ │ │ │ │ │ │ │ │ refcnt 存储位置(objc_retain/release): │ │ │ │ ┌──────────────────────────────────────────────────────────┐ │ │ │ │ │ Small refcount (≤ 1023):存储在 isa.bits[43](12 bits) │ │ │ │ │ │ Large refcount (> 1023):存储在额外 malloc 分配的 refcnt 块 │ │ │ │ │ │ ┌────────────────────────────────────────────────────┐ │ │ │ │ │ │ │ refcnt > 1023 → malloc(sizeof(struct refcnt_alloc)) │ │ │ │ │ │ │ │ struct refcnt_alloc { │ │ │ │ │ │ │ │ uintptr_t refcnt; /* 引用计数 / │ │ │ │ │ │ │ │ uintptr_t extra; / 额外数据 */ │ │ │ │ │ │ │ │ }; │ │ │ │ │ │ │ │ └────────────────────────────────────────────────┘ │ │ │ │ │ └──────────────────────────────────────────────────────────┘ │ │ │ │ │ │ │ │ refcnt 操作的内联汇编(ARM64): │ │ │ │ ┌──────────────────────────────────────────────────────────┐ │ │ │ │ │ retain 操作的 ARM64 汇编: │ │ │ │ │ │ ldp x0, x1, [x29, #0] // load object header │ │ │ │ │ │ ldr x3, [x1, #OBJC_IVAR_OBJC_OBJECT_REF_COUNT] │ │ │ │ │ │ add x3, x3, #1 // refCount += 1 │ │ │ │ │ │ str x3, [x1, #OBJC_IVAR_OBJC_OBJECT_REF_COUNT] │ │ │ │ │ │ └────────────────────────────────────────────────────────┘ │ │ │ │ │ │ │ │ │ │ release 操作的 ARM64 汇编: │ │ │ │ │ │ ldp x0, x1, [x29, #0] // load object header │ │ │ │ │ │ ldr x3, [x1, #OBJC_IVAR_OBJC_OBJECT_REF_COUNT] │ │ │ │ │ │ subs x3, x3, #1 // refCount -= 1 │ │ │ │ │ │ str x3, [x1, #OBJC_IVAR_OBJC_OBJECT_REF_COUNT] │ │ │ │ │ │ cbz x3, free_object // refCount == 0 → dealloc │ │ │ │ │ └──────────────────────────────────────────────────────────┘ │ │ │ └─────────────────────────────────────────────────────────────────┘ │ │ │ │ │ refcnt 优化的 ARM64 指令: │ │ │ ┌───────────────────────────────────────────────────────────────┐ │ │ │ │ LDXR/LSTXR(Load-Exclusive/Store-Exclusive) │ │ │ │ │ → 原子加载/存储(CAS 基础) │ │ │ │ │ │ │ │ │ LDARB/LSTARB(原子屏障) │ │ │ │ │ → 内存屏障,确保 refcnt 操作的顺序性 │ │ │ │ │ │ │ │ │ CLRL/ORN(位操作优化) │ │ │ │ │ → 修改 isa 的特定 bits 而不影响其他 bits │ │ │ │ └───────────────────────────────────────────────────────────────┘ │ │ └─────────────────────────────────────────────────────────────────┘ │ │ │ refcnt 操作性能分析: │ │ ┌─────────────────────────────────────────────────────────────────┐ │ │ 操作 │ ARM64 周期 │ 时间(ns) │ │ │ ├── retain (small refcnt) │ 3-5 cycles │ 1-2ns │ │ │ ├── release (small refcnt) │ 3-5 cycles │ 1-2ns │ │ │ ├── retain (large refcnt) │ 10-15 cycles │ 3-5ns │ │ │ ├── release (large refcnt) │ 10-15 cycles │ 3-5ns │ │ │ ├── dealloc (对象销毁) │ 50-100 cycles │ 15-30ns │ │ │ ├── retain 链 (连续操作) │ 1-2 cycles │ 0.3-0.6ns │ │ │ └── 总 ARC 开销 (一次赋值) │ 8-12 cycles │ 2-4ns │ │ └─────────────────────────────────────────────────────────────────┘ │ │ │ ARC 编译期优化(LLVM 优化阶段): │ │ ┌─────────────────────────────────────────────────────────────────┐ │ │ LLVM 对 ARC 指令的优化策略: │ │ │ ├── retain 配对消除:相邻的 retain + release 被消除 │ │ │ ├── retain 提升:在循环外提升 retain,减少循环内 retain 次数 │ │ │ ├── retain 下沉:将 retain 移到循环内,减少不必要的 retain │ │ │ ├── retain/release 折叠:连续 retain 合并为单次操作 │ │ │ ├── dead code elimination:未使用的 retain/release 被移除 │ │ │ └── tail call optimization:尾调用优化减少栈帧开销 │ │ │ │ │ │ ARC 优化前后对比(示例): │ │ │ ┌───────────────────────────────────────────────────────────────┐ │ │ │ │ // 原始 Swift 代码 │ │ │ │ │ let a = MyClass() // retain count = 1 │ │ │ │ let b = a // retain count = 2 │ │ │ │ a = nil // retain count = 1 │ │ │ │ b = nil // retain count = 0 → dealloc │ │ │ │ │ │ │ │ │ // 优化前 (ARC 生成的 IR) │ │ │ │ │ call void @objc_retain(id) │ │ │ │ │ call void @objc_retain(id) │ │ │ │ │ call void @objc_release(id) │ │ │ │ │ call void @objc_release(id) │ │ │ │ │ │ │ │ │ // 优化后 (LLVM 消除冗余) │ │ │ │ │ call void @objc_retain(id) // 保留 a │ │ │ │ │ call void @objc_release(id) // 释放 b │ │ │ │ │ call void @objc_release(id) // 释放 a │ │ │ │ │ │ │ │ │ // 进一步优化 (LLVM -O3) │ │ │ │ │ // retain 和 release 在相邻作用域被消除 │ │ │ │ │ // 最终可能零 retain/release(如果编译器能证明对象生命周期) │ │ │ │ └───────────────────────────────────────────────────────────────┘ │ │ └─────────────────────────────────────────────────────────────────┘ │ │ │ Swift 对象的内存布局 vs Objective-C 对象: │ │ ┌─────────────────────────────────────────────────────────────────┐ │ │ Objective-C 对象内存布局(64-bit): │ │ │ ┌────────────────────────────────────────────────────────────┐ │ │ │ │ ┌─────────────────────────────────────┐ │ │ │ │ │ │ isa (8 bytes) │ ← 指向类元数据 │ │ │ │ │ ├─────────────────────────────────────┤ │ │ │ │ │ │ ivar_1 (8 bytes) // 实例变量 │ │ │ │ │ │ ├─────────────────────────────────────┤ │ │ │ │ │ │ ivar_2 (8 bytes) // 实例变量 │ │ │ │ │ │ ├─────────────────────────────────────┤ │ │ │ │ │ │ ... (padding/alignment) │ │ │ │ │ │ └─────────────────────────────────────┘ │ │ │ │ │ │ │ │ │ 对象大小 = sizeof(isa) + sizeof(ivars) + padding │ │ │ │ └────────────────────────────────────────────────────────────┘ │ │ │ │ │ │ Swift 对象内存布局(64-bit): │ │ │ ┌───────────────────────────────────────────────────────────────┐ │ │ │ │ ┌─────────────────────────────────────┐ │ │ │ │ │ │ isa (8 bytes) │ │ │ │ │ │ ├─────────────────────────────────────┤ │ │ │ │ │ │ storage (Swift 存储区) │ │ │ │ │ │ │ ┌─────────────────────────────────┐ │ │ │ │ │ │ │ metadata pointer │ → 指向 Swift 元数据 │ │ │ │ │ │ │ value witness table │ → 值操作函数表 │ │ │ │ │ │ │ reference count (refcounted) │ → 引用计数 │ │ │ │ │ │ │ payload (实际存储数据) │ → 对象数据 │ │ │ │ │ │ └─────────────────────────────────┘ │ │ │ │ │ └─────────────────────────────────────┘ │ │ │ │ │ │ │ │ │ Swift 对象的引用计数存储在 storage 中,而不是 isa 中 │ │ │ │ │ │ → 通过 isa 找到 storage,从 storage 获取 refcount │ │ │ │ │ └───────────────────────────────────────────────────────────────┘ │ │ │ │ │ │ Swift 与 Objective-C 内存管理对比: │ │ │ ┌───────────────────────────────────────────────────────────────┐ │ │ │ │ 特性 │ Swift ARC │ Objective-C ARC│ │ │ │ │ ├── 引用计数位置 │ storage 中(间接) │ isa 中(直接) │ │ │ │ │ │ 线程安全 │ 原子操作(CAS) │ 原子操作(CAS) │ │ │ │ │ │ 内存碎片 │ 更小(Storage 布局优化) │ 标准 │ │ │ │ │ │ 性能 │ 略低(间接访问) │ 略高(直接访问)│ │ │ │ │ │ 循环引用检测 │ 编译时警告 + 运行时 │ 运行时检测 │ │ │ │ │ │ unowned │ Swift 特有(不检查) │ 无 │ │ │ │ │ └───────────────────────────────────────────────────────────────┘ │ │ └─────────────────────────────────────────────────────────────────┘ │ │ └─────────────────────────────────────────────────────────────────────┘ */
4.5 内存泄漏检测与修复深度分析
4.5.1 Instruments 与 Address Sanitizer 原理
内存泄漏检测工具深度分析:
┌─────────────────────────────────────────────────────────────────────┐
│ Instruments Allocations 原理: │
│ ┌─────────────────────────────────────────────────────────────────┐
│ │ Allocations 追踪机制: │
│ │ ├── 拦截所有内存分配(malloc/free, alloc/dealloc) │
│ │ ├── 记录每个分配的大小、地址、调用栈 │
│ │ ├── 维护活跃对象图(分配 → 释放 映射) │
│ │ ├── 识别无法释放的对象(泄漏) │
│ │ └── 可视化内存增长趋势 │
│ │ │
│ │ Allocations 的工作流程: │
│ │ ┌──────────────────────────────────────────────────────────────┐ │
│ │ │ 1. 设置 Allocation Tracing │ │
│ │ │ 2. 启动记录 → 捕获所有内存分配 │ │
│ │ │ 3. 执行测试场景 → 观察分配/释放 │ │
│ │ │ 4. 点击 + 按钮 → 分配增量 │ │
│ │ │ 5. 查看 Retained Size → 识别未释放对象 │ │
│ │ │ 6. 点击 Object → 查看引用链(谁持有该对象) │ │
│ │ │ 7. 修复循环引用 │ │
│ │ │ │
│ │ │ 关键指标: │ │
│ │ │ ├── Allocs:当前活跃分配数量 │ │
│ │ │ ├── Live Bytes:当前活跃内存字节数 │ │
│ │ │ ├── Retained Size:对象及其引用的总内存 │ │
│ │ │ ├── Shallow Size:对象本身的内存大小 │ │
│ │ │ └── Zone:分配区域(Default, VM, CG 等) │ │
│ │ └──────────────────────────────────────────────────────────────┘ │
│ │ │
│ │ Allocations 使用示例(检测循环引用): │
│ │ ┌──────────────────────────────────────────────────────────────┐ │
│ │ │ 1. 打开 Xcode → Products → Profile │ │
│ │ │ 2. 选择 Allocations template │ │
│ │ │ 3. 点击红色 + 按钮开始记录 │ │
│ │ │ 4. 执行测试场景(如打开/关闭页面) │ │
│ │ │ 5. 点击 + 按钮获取增量 │ │
│ │ │ 6. 在左侧列表中找到持续增长的对象 │ │
│ │ │ 7. 右键 → Show Retaining Path → 查看引用链 │ │
│ │ │ 8. 定位循环引用的两端对象,添加 weak/unowned │ │
│ │ └──────────────────────────────────────────────────────────────┘ │
│ └─────────────────────────────────────────────────────────────────┘
│ │
│ Address Sanitizer (ASan) 原理: │
│ ┌─────────────────────────────────────────────────────────────────┐
│ │ ASan 的检测原理: │
│ │ ├── 编译时插桩:在每个内存访问前后插入检查代码 │
│ │ ├── 红区(RedZone):在每个对象前后添加保护区域 │
│ │ ├── 影子内存(Shadow Memory):映射堆内存状态 │
│ │ ├── 栈间拷贝检测:检测指针从栈传播到堆 │
│ │ └── 使用计数器(Use-After-Free):检测释放后访问 │
│ │ │
│ │ ASan 的检测类型: │
│ │ ┌──────────────────────────────────────────────────────────────┐ │
│ │ │ ┌───────────────────────────────────────────────────────┐ │ │
│ │ │ │ Heap Buffer Overflow 堆缓冲区溢出 │ │ │
│ │ │ │ → 访问超出分配的堆内存 │ │ │
│ │ │ │ → ASan 通过红区检测 │ │ │
│ │ │ │ ┌───────────────────────────────────────────────────┐ │ │
│ │ │ │ │ [已分配] [红区] [已分配] │ │ │
│ │ │ │ │ ← 访问红区 → ASan 立即崩溃 │ │ │
│ │ │ │ └───────────────────────────────────────────────────┘ │ │
│ │ │ └───────────────────────────────────────────────────────┘ │ │
│ │ │ ┌───────────────────────────────────────────────────────┐ │ │
│ │ │ │ Stack Buffer Overflow 栈缓冲区溢出 │ │ │
│ │ │ │ → 访问超出分配的栈内存 │ │ │
│ │ │ │ → ASan 通过栈帧边界检测 │ │ │
│ │ │ └───────────────────────────────────────────────────────┘ │ │
│ │ │ ┌───────────────────────────────────────────────────────┐ │ │
│ │ │ │ Use After Free 释放后使用 │ │ │
│ │ │ │ → 访问已释放的内存 │ │ │
│ │ │ │ → ASan 通过影子内存检测 │ │ │
│ │ │ └───────────────────────────────────────────────────────┘ │ │
│ │ │ ┌───────────────────────────────────────────────────────┐ │ │
│ │ │ │ Double Free 双重释放 │ │ │
│ │ │ │ → 对同一内存释放两次 │ │ │
│ │ │ │ → ASan 通过元数据检测 │ │ │
│ │ │ └───────────────────────────────────────────────────────┘ │ │
│ │ │ ┌───────────────────────────────────────────────────────┐ │ │
│ │ │ │ Stack Use After Return 栈使用后返回 │ │ │
│ │ │ │ → 访问已返回的栈帧中的数据 │ │ │
│ │ │ │ → ASan 通过栈帧状态检测 │ │ │
│ │ │ └───────────────────────────────────────────────────────┘ │ │
│ │ └──────────────────────────────────────────────────────────────┘ │
│ │ │
│ │ ASan 启用方式: │
│ │ ┌──────────────────────────────────────────────────────────────┐ │
│ │ │ // Xcode 设置: │ │
│ │ │ // 1. Target → Signing & Capabilities │ │
│ │ │ // 2. Enable Address Sanitizer: YES │ │
│ │ │ // 3. 运行 App → 自动检测内存错误 │ │
│ │ │ │
│ │ │ // LLDB 命令行方式: │ │
│ │ │ // settings set target.run-args -sanitize=address │ │
│ │ │ // run │ │
│ │ │ └──────────────────────────────────────────────────────────────┘ │
│ │ │
│ │ ASan vs Allocations 对比: │
│ │ ┌──────────────────────────────────────────────────────────────┐ │
│ │ │ 特性 │ ASan │ Allocations │ │
│ │ │ ├── 检测类型 │ 内存错误(越界、UAF) │ 内存泄漏 │ │
│ │ │ │ 性能影响 │ 约 2x 开销 │ 约 3-10x 开销 │ │
│ │ │ │ 编译要求 │ 需重新编译 │ 无需编译 │ │
│ │ │ │ 适用阶段 │ 开发阶段 │ 生产/开发均可 │ │
│ │ │ │ 报告方式 │ 崩溃 + 堆栈信息 │ 实时图表 │ │
│ │ │ │ 推荐 │ 开发阶段必用 │ 日常检测首选 │ │
│ │ └──────────────────────────────────────────────────────────────┘ │
│ │ │
│ │ ASan 的工作原理(影子内存映射): │
│ │ ┌──────────────────────────────────────────────────────────────┐ │
│ │ │ 每个字节映射到影子内存的一个字节: │ │
│ │ │ ┌─────────────────────────────────────────────────────────┐ │ │
│ │ │ │ 原地址: 0x10000000 0x10000001 0x10000002 0x10000003│ │ │
│ │ │ │ 影子内存: 00 (可用) │ │ │
│ │ │ │ 80 (栈变量) │ │ │
│ │ │ │ 00 (可用) │ │ │
│ │ │ │ FF (已释放) │ │ │
│ │ │ │ FE (红区) │ │ │
│ │ │ │ 01 (堆分配) │ │ │
│ │ │ │ └─────────────────────────────────────────────────────────┘ │ │
│ │ │ │ │
│ │ │ │ 影子值含义: │ │
│ │ │ │ 00 = 可用(可访问) │ │
│ │ │ │ FF = 已释放(Use-After-Free 检测) │ │
│ │ │ │ FE = 红区(缓冲区溢出检测) │ │
│ │ │ │ FD = 栈变量(栈使用后返回检测) │ │
│ │ │ │ 01-7F = 堆分配区域 │ │
│ │ │ └──────────────────────────────────────────────────────────────┘ │
│ └─────────────────────────────────────────────────────────────────┘
│ │
│ 内存泄漏修复策略总结: │
│ ┌─────────────────────────────────────────────────────────────────┐
│ │ 泄漏类型 │ 修复方法 │
│ │ ├── 循环引用 │ weak/unowned 修饰 │
│ │ ├── 闭包捕获 self │ [weak self] / [unowned self] │
│ │ ├── Notification 未移除 │ NotificationCenter.removeObserver │
│ │ ├── Timer 未停止 │ timer.invalidate() │
│ │ ├── Task 未取消 │ task.cancel() │
│ │ ├── 观察者未移除 │ removeObserver │
│ │ ├── 代理未断开 │ delegate = nil │
│ │ ├── 缓存未限制大小 │ 设置 LRU 上限 │
│ │ └── 图片未释放 │ [unowned self] in closure │
│ └─────────────────────────────────────────────────────────────────┘
│ │
└─────────────────────────────────────────────────────────────────────┘
*/
---
## 7.5 内存管理面试深度 Q&A
### 高频面试题(每题 500+ 字详细解答)
**Q1: 请详细解释 ARC 的底层实现原理,包括编译期 ARC 指令插入和运行时的引用计数操作。**
**答**:
ARC(Automatic Reference Counting)是 iOS 的内存管理机制,分为编译期和运行时两个阶段:
**编译期**:编译器(Clang/Swift)在编译时自动插入 retain/release/autorelease 指令。具体而言,当对象赋值、函数返回、作用域结束时,编译器分析对象的生命周期,自动插入对应的 retain/release 调用。对于循环中的临时对象,编译器会插入 autorelease 并创建 AutoreleasePool。编译器还会进行 ARC 优化:消除成对的 retain/release、提升/下沉 retain 位置、折叠连续的 retain 操作等。Swift 编译器还会分析对象的使用模式,在可证明安全的场景下消除多余的 retain/release。
**运行时**:运行时通过 objc_retain/objc_release 等函数执行引用计数操作。每个 Objective-C 对象维护一个引用计数器(存储在 isa 指针的 extra 字段或单独的 refcnt 块中)。retain 操作是原子递增,release 是原子递减并在计数为零时调用 dealloc。 dealloc 调用对象的析构函数(如果有的话),然后调用 free() 释放内存。
**性能**:ARC 的性能接近手动管理,因为编译器优化消除了大部分多余的 retain/release。retain/release 操作约 1-5ns(小 refcnt), dealloc 约 15-30ns。
---
**Q2: 循环引用的常见场景有哪些?如何系统地检测和修复循环引用?**
**答**:
循环引用是 iOS 内存泄漏的最常见原因,常见场景包括:
① **delegate 持有 self**:ViewController 作为 Delegate,ViewModel 持有 ViewController 的强引用。修复:使用 weak var delegate。
② **闭包捕获 self**:闭包捕获 ViewController 的强引用,而 ViewController 持有闭包的强引用。修复:使用 [weak self] 或 [unowned self]。
③ **Timer 持有 self**:NSTimer 持有 target 的强引用。修复:使用 invalidate() 停止 Timer。
④ **NotificationCenter 观察者未移除**:ViewController 作为观察者但从未移除。修复:在 deinit 中调用 removeObserver。
⑤ **嵌套循环引用**:A 持有 B,B 持有 A(直接或间接)。修复:将其中一个改为 weak。
**检测步骤**:① 使用 Instruments → Allocations 追踪分配和释放;② 使用 ASan 检测 Use-After-Free;③ 检查 retain cycle detector 日志;④ 在 deinit 中打印日志验证;⑤ 使用 weak/strong self 工具自动检测。
**Q3: Swift 中 weak 和 unowned 的本质区别是什么?在什么场景下分别使用?**
**答**:
weak 和 unowned 的本质区别在于对象释放后的行为:
**weak**:引用不持有对象,对象释放后自动设为 nil(安全)。weak 变量必须是 optional 类型(因为可能为 nil)。使用场景:① delegate(可能先于 delegate 释放);② 闭包捕获 self(可能先于闭包释放);③ 双向引用中的其中一个。
**unowned**:引用不持有对象,对象释放后不会设为 nil(不安全)。访问已释放的 unowned 对象会触发 EXC_BAD_ACCESS。使用场景:① 已知对象生命周期更长(如父视图持有子视图);② 闭包中需要非 optional 值(性能考虑)。
**unowned unsafe**:与 unowned 类似,但不检查对象的存活状态(性能更高但更危险)。仅用于性能极度敏感的场景。
---
**Q4: AutoreleasePool 的底层实现是什么?在哪些场景下需要手动管理?**
**答**:
AutoreleasePool 的底层实现是 two pointers(begin 和 end)封装的对象。当调用 autorelease 时,对象指针被添加到当前线程的 pool 栈的 end 位置。当 pool 被 drain 时,遍历 begin 到 end 之间的所有对象调用 release。
**主线程自动管理**:主线程的 RunLoop 每个迭代自动创建和释放 pool。
**手动管理的场景**:① 子线程中(不会自动创建);② 大循环中(避免内存峰值过高);③ 异步任务中(RunLoop 不自动管理);④ 图片处理等密集分配场景中。
**性能影响**:不手动管理 pool 可能导致内存峰值是正常情况的 3-10 倍。例如,在循环中创建 10000 个对象而不使用 pool,所有对象都要等到线程结束才释放,峰值内存极高。
---
**Q5: iOS 内存布局的五个区域(Stack、Heap、Data、BSS、Text)各有什么特点?**
**答**:
**Stack(栈)**:局部变量、函数参数、返回地址。自动管理(push/pop),容量有限(默认 1-8MB),LIFO 结构。访问速度最快(CPU 寄存器)。
**Heap(堆)**:动态分配(alloc/init)。手动管理(ARC),容量大(可用内存),碎片化。访问速度中等(通过指针间接访问)。
**Data(数据段)**:已初始化的全局变量和静态变量。初始化后不变。容量取决于程序大小。
**BSS(未初始化数据段)**:未初始化的全局变量和静态变量。在程序启动时自动初始化为零。容量取决于全局变量大小。
**Text(代码段)**:程序指令(机器码)。只读。容量取决于代码大小。
---
**Q6: Instruments 的 Allocations 和 Leaks 工具各自的工作原理是什么?**
**答**:
**Allocations**:拦截所有内存分配,记录每个分配的调用栈、大小、时间戳。维护活跃对象图,识别泄漏(分配了但未释放的对象)。提供实时内存增长图表和对象图可视化。
**Leaks**:基于 Allocations 的堆栈追踪,自动检测无法访问的内存(即泄漏)。当进程退出或手动检测时,报告所有泄漏对象及其保留路径。
**配合使用**:先用 Allocations 追踪分配/释放,发现泄漏后在 Leaks 中查看具体的泄漏对象和引用链。
---
**Q7: iOS 内存压力(Memory Pressure)如何处理?didReceiveMemoryWarning 的触发机制?**
**答**:
**触发机制**:当系统内存不足时,iOS 向所有前台 App 发送 Memory Pressure 通知(从 Low 到 Warning 到 Critical)。在 Critical 级别,系统会终止后台 App 以释放内存。
**处理方法**:① 在 didReceiveMemoryWarning 中释放视图缓存、图片缓存;② 停止非必要的网络请求和定时器;③ 释放大型数据结构(如图像缓冲区);④ 使用低内存模式(低质量图片、简化 UI);⑤ 在 App 进入后台时主动释放内存。
**注意**:iOS 5+ 中 didReceiveMemoryWarning 只在特定条件下触发(如视图控制器未出现在窗口上时)。现代 iOS 更多依赖 Memory Pressure 通知而非 didReceiveMemoryWarning。
---
**Q8: Swift 和 Objective-C 的内存管理有何异同?**
**答**:
**相同点**:① 都使用引用计数(ARC);② 都是自动内存管理;③ 都需要处理循环引用;④ 都有 weak/unowned。
**不同点**:① Swift 的 ARC 在编译期由 Swift 编译器处理,ObjC 由 Clang 处理;② Swift 的 unowned 是 Swift 特有的,ObjC 无对应物;③ Swift 的内存管理更安全(编译时检查);④ Swift 的引用计数存储在 storage 中(间接),ObjC 存储在 isa 中(直接);⑤ Swift 的 Copy-on-Write(COW)优化使值类型更高效;⑥ ObjC 支持手动 retain/release(MRC),Swift 不支持。
---
**Q9: 如何检测和预防内存泄漏?内存泄漏与崩溃的关系?**
**答**:
**检测手段**:① Instruments → Allocations(实时追踪);② Instruments → Leaks(自动检测);③ Address Sanitizer(编译时插桩);④ 代码审查(检查 weak/unowned);⑤ 自动化测试(内存检测断言)。
**预防手段**:① 始终使用 weak 修饰 delegate;② 闭包中始终使用 [weak self];③ Timer 和 Notification 在 deinit 中清理;④ 使用现代 API(Combine/SwiftUI 自动管理);⑤ 定期运行 ASan 和 Allocations。
**内存泄漏与崩溃的关系**:内存泄漏本身不直接导致崩溃,但长期泄漏会导致内存不足,触发 OOM(Out of Memory)崩溃。此外,Use-After-Free(ASan 检测的类型)直接导致崩溃。