Swift 的 自动引用计数(Automatic Reference Counting,简称 ARC) 是 Swift 管理内存的核心机制,仅适用于类(class)实例(结构体和枚举是值类型,不受 ARC 管理)。
ARC 自动跟踪对象的引用数量,当引用计数为 0 时,自动释放内存,无需手动 alloc/release。
一、ARC 核心原理
| 事件 | 引用计数变化 |
|---|
强引用创建(let obj = Class()) | +1 |
强引用复制(let obj2 = obj) | +1 |
| 强引用离开作用域 | -1 |
| 引用计数归零 | 自动调用 deinit → 释放内存 |
class Person {
let name: String
init(name: String) { self.name = name }
deinit { print("\(name) 被释放") }
}
var p1: Person? = Person(name: "Alice") // 计数: 1
var p2 = p1 // 计数: 2
p1 = nil // 计数: 1
p2 = nil // 计数: 0 → deinit
// 输出: Alice 被释放
二、引用类型
| 类型 | 说明 | 是否影响 ARC |
|---|
| 强引用(Strong) | 默认 | 增加引用计数 |
| 弱引用(Weak) | weak var | 不增加计数,自动为 nil |
| 无主引用(Unowned) | unowned var | 不增加计数,假设始终存在 |
三、强引用循环(Retain Cycle)→ 内存泄漏
class Person {
var apartment: Apartment?
deinit { print("Person 释放") }
}
class Apartment {
var tenant: Person? // 强引用
deinit { print("Apartment 释放") }
}
var person: Person? = Person()
var apt: Apartment? = Apartment()
person?.apartment = apt
apt?.tenant = person
person = nil
apt = nil
// 无输出!内存泄漏!
解决:使用 weak 或 unowned
class Apartment {
weak var tenant: Person? // 弱引用
// 或:unowned var tenant: Person!
deinit { print("Apartment 释放") }
}
person = nil
apt = nil
// 输出:
// Person 释放
// Apartment 释放
四、weak vs unowned
| 特性 | weak | unowned |
|---|
| 可选性 | Optional | 非可选 |
自动置 nil | Yes | No |
| 访问时 | tenant?.name | tenant.name |
| 风险 | 安全 | 访问已释放对象 → 崩溃 |
| 适用场景 | 父子关系、委托 | 生命周期明确(如 self 在闭包中) |
// weak:安全
weak var delegate: SomeProtocol?
// unowned:高效但危险
unowned let owner: UIViewController
五、闭包中的强引用循环(常见!)
class Counter {
var value = 0
lazy var increment: () -> Void = {
self.value += 1 // 闭包强引用 self
}
deinit { print("Counter 释放") }
}
var counter: Counter? = Counter()
counter?.increment()
counter = nil
// 无输出!内存泄漏!
解决:捕获列表 [weak self] / [unowned self]
lazy var increment: () -> Void = { [weak self] in
guard let self = self else { return }
self.value += 1
}
// 或
lazy var increment: () -> Void = { [unowned self] in
self.value += 1
}
六、ARC 工作流程图
┌──────────────┐
│ 创建实例 │ → 引用计数 = 1
└──────┬───────┘
↓
┌──────────────┐
│ 强引用复制 │ → 计数 += 1
└──────┬───────┘
↓
┌──────────────┐
│ 引用离开作用域 │ → 计数 -= 1
└──────┬───────┘
↓
┌─────────┐
│ 计数 == 0│ → 调用 deinit → 释放内存
└─────────┘
七、ARC 优化技巧
1. 避免不必要的强引用
// 错误:Timer 强引用 self
Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { _ in
self.updateUI() // 强引用循环!
}
// 正确:
Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { [weak self] _ in
self?.updateUI()
}
2. 使用 [weak self] 成为惯例
network.request { [weak self] result in
guard let self = self else { return }
self.handle(result)
}
3. 临时强引用(避免中途释放)
func processAsync(completion: @escaping () -> Void) {
let strongSelf = self
Task {
await heavyWork()
strongSelf.updateUI() // 确保 self 存活
}
}
八、ARC 与 struct 对比
| 特性 | class (ARC) | struct |
|---|
| 内存管理 | 堆 + ARC | 栈/内联 |
| 复制行为 | 引用 | 值复制 |
| 引用循环 | 可能 | 不可能 |
deinit | 支持 | 不支持 |
| 性能 | 稍慢(指针) | 更快(无引用计数) |
推荐:能用 struct 就用 struct
九、调试内存泄漏
1. Xcode Instruments → Leaks
2. Memory Graph Debugger
3. 打印 deinit
deinit {
print("释放: \(self)")
}
4. 启用 Zombie Objects(捕获野指针)
十、完整实战:ViewController 内存管理
class ProfileViewController: UIViewController {
var user: User?
private var timer: Timer?
override func viewDidLoad() {
super.viewDidLoad()
timer = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { [weak self] _ in
self?.updateTime()
}
}
deinit {
timer?.invalidate()
print("ProfileVC 释放")
}
}
最佳实践总结
| 建议 | 说明 |
|---|
默认使用 struct | 无 ARC 负担 |
闭包一律 [weak self] | 防止 retain cycle |
委托用 weak | 打破循环 |
生命周期明确用 unowned | 性能优先 |
deinit 中清理资源 | Timer、Notification、KVO |
避免 strong 捕获 self | 除非必要 |
常见面试题
- ARC 是什么?如何工作?
→ 自动引用计数,跟踪强引用数量,归零时释放。
- 如何打破强引用循环?
→ 使用 weak 或 unowned
- 闭包中为什么用
[weak self]?
→ 防止 self 和闭包互相强引用。
weak 和 unowned 什么时候用?
weak: 可选,安全
unowned: 必存在,高效
struct 需要 ARC 吗?
→ 不需要,值类型复制传递。
小技巧
// 1. 安全执行
[weak self].map { $0?.doSomething() }
// 2. 自动断开 KVO
class Observer: NSObject {
@objc dynamic var value: Int = 0
private var observation: NSKeyValueObservation?
func start() {
observation = observe(\.value, options: .new) { _, _ in
print("变化")
}
}
deinit {
observation?.invalidate() // 自动清理
}
}
// 3. 使用 @MainActor 隔离
@MainActor
class ViewModel {
func update() { ... }
}
高级话题(可继续提问)
- ARC 底层实现(retain/release)
- Swift 与 Objective-C ARC 混编
- 循环引用检测工具原理
- Actor 与 ARC
- Copy-on-Write 与 ARC 优化
需要 Instruments 内存泄漏排查实战、复杂闭包 ARC 分析,或 自定义弱引用容器?欢迎继续提问!