内存管理
一、ARC(Automatic Reference Counting)机制
什么是 ARC?
ARC 是编译器层面的内存管理机制,自动在合适的位置插入 retain、release 和 autorelease 调用,从而减少开发者手动管理内存的负担。
它的核心思想是引用计数(Reference Counting):每个对象维护一个引用计数,当引用计数为 0 时,对象被释放。
ARC 的基本原理
- 当创建一个对象时(例如 let obj = MyClass()),系统为它分配内存并将引用计数设为 1。
- 每当有新的强引用(strong)指向该对象时,引用计数 +1。
- 当某个强引用被释放或置为 nil 时,引用计数 -1。
- 当引用计数降为 0 时,系统调用 deinit 销毁对象。
二、引用类型与内存泄漏
Strong(强引用)
默认引用类型。对象被强引用时不会被释放。
示例:
class Person {
var pet: Dog?
}
class Dog {
var owner: Person?
}
var p: Person? = Person()
var d: Dog? = Dog()
p?.pet = d
d?.owner = p // 强引用循环!问题: Person 和 Dog 相互强引用,导致两者的引用计数永远不会为 0,发生内存泄漏(retain cycle)。
Weak(弱引用)
不会增加引用计数,引用对象释放后会自动变为 nil。
适用于可选类型(必须为 var,且为 Optional)。
class Dog {
weak var owner: Person?
}常见使用场景:
- Delegate(代理)模式(例如 delegate 通常为 weak)
- 闭包中避免循环引用([weak self])
Unowned(无主引用)
不会增加引用计数,对象释放后不会自动置为 nil,若访问已释放对象会导致崩溃。
class CreditCard {
unowned let owner: Customer
init(owner: Customer) {
self.owner = owner
}
}使用场景:
- 两个对象生命周期绑定,其中一个一定晚于另一个释放。
- 例如父对象持有子对象的强引用,而子对象通过 unowned 引用父对象。
三、循环引用(Retain Cycle)
1. 对象间循环引用
class A {
var b: B?
}
class B {
var a: A?
}解决:让其中一个引用为 weak 或 unowned。
2. 闭包循环引用
闭包会捕获 self,导致对象与闭包相互引用。
解决方法:
class ViewController {
var handler: (() -> Void)?
func setup() {
handler = { [weak self] in
self?.doSomething()
}
}
}四、AutoreleasePool
概念
AutoreleasePool 用于延迟释放对象。
在 Swift 中较少直接使用,但在与 Objective-C 交互或创建大量临时对象时仍有意义。
示例:
autoreleasepool {
for i in 0..<1000 {
let obj = NSString(format: "Item %d", i)
// 临时对象在此作用域结束后被释放
}
}系统层面
RunLoop 每次循环时都会自动创建并销毁一个 AutoreleasePool,确保局部临时对象及时释放。
五、内存管理在多线程下的注意点
- 引用计数操作是原子性的,但对象的使用不是线程安全的。
- 不同线程同时访问同一对象可能导致崩溃,需要使用同步机制(如 DispatchQueue、NSLock)。
- AutoreleasePool 作用域与线程绑定:每个线程需自行管理其池。
六、内存泄漏的检测手段
- Instruments → Leaks / Allocations:可视化查看内存曲线与泄漏对象。
- Xcode Memory Graph Debugger:直接在运行时查看对象引用关系。
- weak self 检查:常见于闭包、异步回调。
- 工具库:如 FBRetainCycleDetector 自动检测循环引用。
七、面试常见问题总结
| 问题 | 答案要点 |
|---|---|
| ARC 是编译器还是运行时特性? | 编译器特性,通过插入 retain/release 代码实现。 |
| weak 和 unowned 的区别? | weak 自动置 nil、安全但可选;unowned 不置 nil、风险更高但效率略高。 |
| 闭包捕获 self 导致循环引用如何解决? | 使用 [weak self] 或 [unowned self]。 |
| AutoreleasePool 的作用? | 控制临时对象生命周期,防止瞬时内存峰值。 |
| Swift 的结构体和类在内存管理上有何区别? | 结构体是值类型,存放在栈上;类是引用类型,存放在堆上并受 ARC 管理。 |
要真正掌握内存管理,关键是理解生命周期与所有权的关系,ARC 只是帮你执行“清理”,但逻辑上仍要靠开发者设计清晰的引用结构。