ARC 内存管理

解构自动引用计数原理。掌握强引用、弱引用及无主引用的使用场景,彻底杜绝 Retain Cycles。

引用计数不是垃圾回收,理解内存生命周期是 iOS 开发的分水岭。掌握 ARC 是写出高质量代码的基础。


1. ARC 工作原理

ARC (Automatic Reference Counting) 是 Swift 和 Objective-C 的内存管理机制。

引用计数基础

class Person {
    let name: String
    init(name: String) {
        self.name = name
        print("\(name) 被初始化")
    }
    deinit {
        print("\(name) 被释放")
    }
}

var person1: Person? = Person(name: "张三")  // 引用计数: 1
var person2 = person1                         // 引用计数: 2
var person3 = person1                         // 引用计数: 3

person1 = nil  // 引用计数: 2
person2 = nil  // 引用计数: 1
person3 = nil  // 引用计数: 0 → 调用 deinit,对象被释放

引用计数存储

Swift 对象的引用计数存储在对象头部:

┌─────────────────────────────────────┐
│           Object Header             │
├─────────────────────────────────────┤
│  Strong RC  │  Unowned RC  │ Flags  │
├─────────────────────────────────────┤
│           Object Data               │
└─────────────────────────────────────┘

2. 强引用循环 (Retain Cycles)

当两个对象互相持有对方的强引用时,引用计数永远不会归零,导致内存泄漏。

典型场景

class Person {
    let name: String
    var apartment: Apartment?
    
    init(name: String) { self.name = name }
    deinit { print("\(name) 被释放") }
}

class Apartment {
    let unit: String
    var tenant: Person?  // 强引用 → 循环引用!
    
    init(unit: String) { self.unit = unit }
    deinit { print("公寓 \(unit) 被释放") }
}

var john: Person? = Person(name: "John")
var unit4A: Apartment? = Apartment(unit: "4A")

john?.apartment = unit4A
unit4A?.tenant = john  // 形成循环引用

john = nil      // Person 不会释放
unit4A = nil    // Apartment 不会释放
// 内存泄漏!

解决方案:weak 引用

class Apartment {
    let unit: String
    weak var tenant: Person?  // 弱引用,打破循环
    
    init(unit: String) { self.unit = unit }
    deinit { print("公寓 \(unit) 被释放") }
}

// 现在设置为 nil 时,两个对象都能正确释放

3. weak vs unowned

特性 weak unowned
类型 Optional Non-optional
释放后的值 自动设为 nil 保持原地址(悬垂指针)
访问已释放对象 返回 nil 崩溃
使用场景 可能比自己先释放 确定不会比自己先释放
性能 略慢(需要维护弱引用表) 略快

weak 使用示例

class Customer {
    let name: String
    var card: CreditCard?
    
    init(name: String) { self.name = name }
    deinit { print("\(name) 被释放") }
}

class CreditCard {
    let number: UInt64
    weak var customer: Customer?  // 客户可能先被释放
    
    init(number: UInt64, customer: Customer) {
        self.number = number
        self.customer = customer
    }
    deinit { print("卡号 \(number) 被释放") }
}

unowned 使用示例

class Country {
    let name: String
    var capitalCity: City!  // 隐式解包可选
    
    init(name: String, capitalName: String) {
        self.name = name
        self.capitalCity = City(name: capitalName, country: self)
    }
}

class City {
    let name: String
    unowned let country: Country  // 国家一定比城市存在更久
    
    init(name: String, country: Country) {
        self.name = name
        self.country = country
    }
}

4. 闭包中的循环引用

闭包会捕获其引用的对象,这是循环引用的常见来源。

问题场景

class ViewController: UIViewController {
    var handler: (() -> Void)?
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        handler = {
            // self 被强引用捕获
            self.doSomething()  // 循环引用!
        }
    }
    
    func doSomething() { }
    
    deinit {
        print("ViewController 被释放")  // 永远不会调用
    }
}

解决方案:捕获列表

class ViewController: UIViewController {
    var handler: (() -> Void)?
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        // 使用 [weak self] 打破循环
        handler = { [weak self] in
            guard let self = self else { return }
            self.doSomething()
        }
        
        // 或者使用 [unowned self](确保 self 一定存在)
        handler = { [unowned self] in
            self.doSomething()
        }
    }
}

捕获多个值

class DataManager {
    var data: [String] = []
    var processor: DataProcessor?
    
    func setup() {
        processor?.onComplete = { [weak self, weak processor] result in
            self?.data = result
            processor?.cleanup()
        }
    }
}

5. 常见内存泄漏场景

1. Timer 强引用

class TimerViewController: UIViewController {
    var timer: Timer?
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        // ❌ 错误:Timer 强引用 self
        timer = Timer.scheduledTimer(
            timeInterval: 1.0,
            target: self,
            selector: #selector(tick),
            userInfo: nil,
            repeats: true
        )
    }
    
    // ✅ 正确:使用闭包版本
    override func viewDidLoad() {
        super.viewDidLoad()
        
        timer = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { [weak self] _ in
            self?.tick()
        }
    }
    
    override func viewWillDisappear(_ animated: Bool) {
        super.viewWillDisappear(animated)
        timer?.invalidate()
    }
    
    @objc func tick() { }
}

2. NotificationCenter

class NotificationViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
        
        // iOS 9+ 自动移除观察者,但使用闭包时要注意
        NotificationCenter.default.addObserver(
            forName: .someNotification,
            object: nil,
            queue: .main
        ) { [weak self] notification in
            self?.handleNotification(notification)
        }
    }
}

3. Delegate 循环

// ❌ 错误:强引用 delegate
protocol DataSourceDelegate: AnyObject { }

class DataSource {
    var delegate: DataSourceDelegate?  // 应该是 weak
}

// ✅ 正确
class DataSource {
    weak var delegate: DataSourceDelegate?
}

6. 检测内存泄漏

Xcode Memory Graph

  1. 运行 App
  2. 点击 Debug Navigator
  3. 点击 “Debug Memory Graph” 按钮
  4. 查看对象引用关系

Instruments - Leaks

  1. Product → Profile (⌘I)
  2. 选择 “Leaks” 模板
  3. 运行并观察泄漏

代码检测

// 在 deinit 中打印,确认对象被释放
class MyViewController: UIViewController {
    deinit {
        print("✅ MyViewController 被正确释放")
    }
}

7. 值类型 vs 引用类型

值类型(Struct, Enum)

struct Point {
    var x: Double
    var y: Double
}

var p1 = Point(x: 0, y: 0)
var p2 = p1  // 复制,不是引用
p2.x = 10
print(p1.x)  // 0(不受影响)

引用类型(Class)

class PointClass {
    var x: Double
    var y: Double
    init(x: Double, y: Double) {
        self.x = x
        self.y = y
    }
}

var p1 = PointClass(x: 0, y: 0)
var p2 = p1  // 引用同一对象
p2.x = 10
print(p1.x)  // 10(被修改)

选择建议

场景 推荐类型
数据模型(不需要继承) Struct
需要继承 Class
需要 identity(===) Class
需要 deinit Class
跨线程共享 Actor

8. 最佳实践

✅ 推荐做法

// 1. Delegate 使用 weak
protocol ViewDelegate: AnyObject { }
class View {
    weak var delegate: ViewDelegate?
}

// 2. 闭包使用捕获列表
completion = { [weak self] in
    self?.handleComplete()
}

// 3. 在 deinit 中清理资源
deinit {
    NotificationCenter.default.removeObserver(self)
    timer?.invalidate()
}

// 4. 优先使用值类型
struct User {
    let id: UUID
    var name: String
}

❌ 避免的做法

// 1. 不要在闭包中直接使用 self
handler = {
    self.doSomething()  // ❌ 可能循环引用
}

// 2. 不要忘记 invalidate Timer
// timer 一直运行,ViewController 无法释放

// 3. 不要过度使用 unowned
// 如果不确定生命周期,使用 weak 更安全