Swift 自动引用计数(ARC)

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
// 无输出!内存泄漏!

解决:使用 weakunowned

class Apartment {
    weak var tenant: Person?  // 弱引用
    // 或:unowned var tenant: Person!
    deinit { print("Apartment 释放") }
}

person = nil
apt = nil
// 输出:
// Person 释放
// Apartment 释放

四、weak vs unowned

特性weakunowned
可选性Optional非可选
自动置 nilYesNo
访问时tenant?.nametenant.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除非必要

常见面试题

  1. ARC 是什么?如何工作?
    → 自动引用计数,跟踪强引用数量,归零时释放。
  2. 如何打破强引用循环?
    → 使用 weakunowned
  3. 闭包中为什么用 [weak self]
    → 防止 self 和闭包互相强引用。
  4. weakunowned 什么时候用?
  • weak: 可选,安全
  • unowned: 必存在,高效
  1. 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 分析,或 自定义弱引用容器?欢迎继续提问!

文章已创建 2481

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

相关文章

开始在上面输入您的搜索词,然后按回车进行搜索。按ESC取消。

返回顶部