Automatic Reference Counting (ARC) — Swift和Objective-C中的内存管理系统,自动计算每个对象的引用数量,并在计数器达到零时释放它。据Apple Swift Documentation, 2026,ARC嵌入在编译器中,在编译阶段工作,在合适的位置插入retain/release调用。与Garbage Collection不同,ARC不需要单独的收集线程,也不会在应用程序执行期间创建暂停。
核心要点
ARC(Automatic Reference Counting) — 是苹果在Xcode 4.2(2011)中为Objective-C引入的编译器级内存管理机制,并被Swift继承。与手动内存管理(Manual Retain-Release,MRR)不同,ARC完全自动化了retain、release和autorelease调用,在编译阶段插入它们而无需开发者介入。
ARC不是垃圾回收器。它是动态代码插入的静态分析:编译器分析对象的寿命,并在对象被创建、拷贝或离开作用域时放置retain/release。结果是确定性的内存释放:对象在没有引用它时立即删除,没有延迟和暂停。
据WWDC 2011 Session 323,从MRR迁移到ARC使Apple应用中与内存相关的崩溃错误减少了70%。开发者不再手动平衡retain/release,消除了整个类别的内存泄漏和double-free错误。
内存中的每个对象都有一个引用计数器(retain count)。创建对象时,计数器被设为1。当新的strong引用指向对象时,计数器增加(retain)。当strong引用消失时,计数器减少(release)。达到零时,对象立即被释放。
Swift编译器不会在每次赋值时插入retain/release — 它使用静态分析进行优化。例如,如果一个对象被保证在传递后不会被使用,编译器可以跳过多余的release/retain。这种优化称为ARC Optimisation。
class Person {
let name: String
init(name: String) {
self.name = name
print("\(name)初始化(retain count: 1)")
}
deinit {
print("\(name)被释放")
}
}
func testARC() {
let p = Person(name: "Alice") // retain count = 1
let q = p // retain count = 2
// q离开作用域
// retain count = 1
// p离开作用域
// retain count = 0 → deinit
}
在这个例子中可以看到ARC如何管理计数器:在赋值 q = p 时,计数器增加;当q离开作用域时,计数器减少。当最后一个strong引用消失时,解构函数立即被调用。没有任何垃圾回收器等待 — 内存立即释放。
ARC和Garbage Collection解决相同的任务 — 自动内存管理 — 但采用根本不同的方法。之间的选择决定了语言架构:Swift(ARC)vs Java/Go(GC)。让我们来看看主要差异。
| 特征 | ARC (Swift/ObjC) | GC (Java/Go) |
|---|---|---|
| 释放时机 | 确定性:计数器归零时立即 | 非确定性:下次回收时 |
| 执行暂停 | 无(在编译阶段插入retain/release) | 有Stop-The-World暂停(2–200毫秒) |
| 额外开销 | 每次引用时增加/减少计数器 | 遍历对象图、标记、释放 |
| 问题 | Retain Cycle(手动解决) | 堆碎片化、遗忘引用导致的泄漏 |
| 额外线程 | 不需要 | 需要垃圾回收器线程 |
关键妥协:ARC提供可预测的对象寿命和零暂停,但要求开发者理解retain cycle并正确选择weak/unowned。GC解放了这些顾虑,但付出了非确定性暂停和额外线程的代价。
ARC定义了三种引用修饰符类型,每种类型对计数器和对象生命周期的影响不同。正确选择修饰符是Swift中安全内存操作的基础。
Strong — 默认修饰符。每个strong引用将对象的retain count增加1。只要至少有一个strong引用存在,对象就是活的。Swift中所有的类属性和局部变量默认都是strong。Strong引用创建所有权关系:对象A拥有对象B。
Weak — 不增加retain count的引用。对象可以被释放,即使有weak引用指向它。释放后,weak引用自动被设为nil。Weak引用始终被声明为带可选类型(?)的var。它们用于打断retain cycle,特别是在delegate模式中。
Unowned — 非拥有引用,与weak一样不增加retain count。但是unowned引用在释放后不会被设为nil — 访问已释放的对象会导致崩溃。Unowned用于当保证对象至少像引用它的对象一样长寿时。典型场景 — 闭包(closures)和保证寿命的父母-子女关系。
class Customer {
let name: String
var card: CreditCard? // strong
init(name: String) { self.name = name }
deinit { print("\(name)被释放") }
}
class CreditCard {
let number: String
unowned let customer: Customer // unowned — 不拥有
init(number: String, customer: Customer) {
self.number = number
self.customer = customer
}
deinit { print("卡片\(number)被释放") }
}
var customer: Customer? = Customer(name: "Bob")
customer?.card = CreditCard(number: "1234", customer: customer!)
customer = nil
// Customer和CreditCard均被释放 — 没有retain cycle
这里CreditCard使用unowned引用指向Customer。Customer拥有卡(strong),卡不拥有客户(unowned)。当Customer变为nil时,两个对象都被释放 — 不会产生retain cycle。如果card.customer是strong,循环将阻止释放。
尽管自动化,ARC并非万能药。开发者遇到几个典型问题,需要理解内存管理的内部机制。
Swift中的闭包(closures)通过strong引用捕获外部变量。如果闭包被赋值给类属性并捕获了self — 就会产生retain cycle:类拥有闭包,闭包拥有self。解决方案 — 使用weak或unowned的capture list。
class NetworkManager {
var completionHandler: ((Data?) -> Void)?
var data: Data?
func fetchData() {
completionHandler = { [weak self] result in
guard let self else { return }
self.data = result
self.processResult()
}
}
func processResult() { }
}
Capture list [weak self]在闭包内部创建一个向self的weak引用。这打断了潜在的retain cycle。Guard let self保证在执行代码前对象是活的。weak self — Swift中异步闭包的标准实践。
尽管retain/release是轻量级操作,在热循环中频繁增加/减少计数器会产生额外开销。在Swift 5.9+中,编译器使用优化,如果分析器能证明安全性,多余的retain/release将被移除。然而,在Objective-C中,retail/release在高负荷场景下(每秒数百万次调用)仍可能是瓶颈。
Autorelease Pool — 延迟release机制,用于Objective-C和一些Swift场景。对象被放置在池中,并在池被排放时接收release。在具有大量临时对象的循环中(例如解析JSON),创建自己的autoreleasepool可以减少运行时内存的峰值消耗。
常见问题
在手动管理(MRR)中,开发者显式调用retain、release和autorelease。ARC在编译阶段自动插入这些调用,消除了double-free风险、因遗忘release导致的泄漏以及retain/release平衡错误。
ARC只管理Objective-C对象和Swift类。对于C/C++结构体和指针,ARC不遢用 — 这些对象通过手动方式或C++智能指针(shared_ptr、unique_ptr)管理。Core Foundation对象(CFString、CGColor)也不属于ARC范畴。
weak — 当对象可能比引用它的对象更早被释放时(代理、异步闭包)。unowned — 当保证对象至少和引用它的对象一样长寿时(父母-子女,子女不能离开父母存在)。如果不确定 — 选择weak。
Swift中的存在类型(protocol as type)将值封装在一个特殊容器(existential container)中。这增加了在协议边界上的retain/release次数。在Swift 5.7+中,opaque result types和some参数通过取消容器来减少额外开销。
Swift中没有直接读取retain count的API — 这被视为实现细节。针对诊断,请使用Xcode中的Instruments(Allocations、Leaks)或Memory Debugger。这些工具显示类的活实例数量和保留链。
总结
我们将开发一款交钥匙移动应用程序
IT Sectr自2017年以来为初创企业和企业打造iOS和Android应用程序。我们将为您提供咨询并提出最佳解决方案。