ARC是什么?iOS中Automatic Reference Counting的工作原理

作者: IT Sectr 发布日期: 2026-03-29 阅读时间: 8 分钟

Automatic Reference Counting (ARC) — Swift和Objective-C中的内存管理系统,自动计算每个对象的引用数量,并在计数器达到零时释放它。据Apple Swift Documentation, 2026,ARC嵌入在编译器中,在编译阶段工作,在合适的位置插入retain/release调用。与Garbage Collection不同,ARC不需要单独的收集线程,也不会在应用程序执行期间创建暂停。

核心要点

  • ARC — Automatic Reference Counting,Swift和Objective-C中的编译器内存管理系统
  • 工作原理 — 每个对象都有一个引用计数器(retain count),归零时对象立即释放
  • 修饰符 — strong、weak和unowned确定引用如何影响计数器和对象的生命周期
  • 与GC的区别 — ARC在编译阶段确定性工作,没有Stop-The-World暂停和后台收集线程
  • Retain Cycle — ARC的主要问题:如果两个对象通过strong互相引用,它们的计数器永远不会归零

什么是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错误。

Automatic Reference Counting如何工作

内存中的每个对象都有一个引用计数器(retain count)。创建对象时,计数器被设为1。当新的strong引用指向对象时,计数器增加(retain)。当strong引用消失时,计数器减少(release)。达到零时,对象立即被释放。

Swift编译器不会在每次赋值时插入retain/release — 它使用静态分析进行优化。例如,如果一个对象被保证在传递后不会被使用,编译器可以跳过多余的release/retain。这种优化称为ARC Optimisation

swift
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:主要区别

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解放了这些顾虑,但付出了非确定性暂停和额外线程的代价。

Strong、Weak和Unowned:ARC中的引用修饰符

ARC定义了三种引用修饰符类型,每种类型对计数器和对象生命周期的影响不同。正确选择修饰符是Swift中安全内存操作的基础。

Strong

Strong — 默认修饰符。每个strong引用将对象的retain count增加1。只要至少有一个strong引用存在,对象就是活的。Swift中所有的类属性和局部变量默认都是strong。Strong引用创建所有权关系:对象A拥有对象B。

Weak

Weak — 不增加retain count的引用。对象可以被释放,即使有weak引用指向它。释放后,weak引用自动被设为nil。Weak引用始终被声明为带可选类型(?)的var。它们用于打断retain cycle,特别是在delegate模式中。

Unowned

Unowned — 非拥有引用,与weak一样不增加retain count。但是unowned引用在释放后不会被设为nil — 访问已释放的对象会导致崩溃。Unowned用于当保证对象至少像引用它的对象一样长寿时。典型场景 — 闭包(closures)和保证寿命的父母-子女关系。

swift
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常见问题及解决方案

尽管自动化,ARC并非万能药。开发者遇到几个典型问题,需要理解内存管理的内部机制。

闭包中的Retain Cycle

Swift中的闭包(closures)通过strong引用捕获外部变量。如果闭包被赋值给类属性并捕获了self — 就会产生retain cycle:类拥有闭包,闭包拥有self。解决方案 — 使用weak或unowned的capture list。

swift
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性能

尽管retain/release是轻量级操作,在热循环中频繁增加/减少计数器会产生额外开销。在Swift 5.9+中,编译器使用优化,如果分析器能证明安全性,多余的retain/release将被移除。然而,在Objective-C中,retail/release在高负荷场景下(每秒数百万次调用)仍可能是瓶颈。

Autorelease Pool

Autorelease Pool — 延迟release机制,用于Objective-C和一些Swift场景。对象被放置在池中,并在池被排放时接收release。在具有大量临时对象的循环中(例如解析JSON),创建自己的autoreleasepool可以减少运行时内存的峰值消耗。

常见问题

ARC与手动内存管理(MRR)有什么区别?

在手动管理(MRR)中,开发者显式调用retain、release和autorelease。ARC在编译阶段自动插入这些调用,消除了double-free风险、因遗忘release导致的泄漏以及retain/release平衡错误。

ARC能与C/C++代码一起工作吗?

ARC只管理Objective-C对象和Swift类。对于C/C++结构体和指针,ARC不遢用 — 这些对象通过手动方式或C++智能指针(shared_ptr、unique_ptr)管理。Core Foundation对象(CFString、CGColor)也不属于ARC范畴。

什么时候用weak,什么时候用unowned?

weak — 当对象可能比引用它的对象更早被释放时(代理、异步闭包)。unowned — 当保证对象至少和引用它的对象一样长寿时(父母-子女,子女不能离开父母存在)。如果不确定 — 选择weak

什么是存在类型,它们如何影响ARC?

Swift中的存在类型(protocol as type)将值封装在一个特殊容器(existential container)中。这增加了在协议边界上的retain/release次数。在Swift 5.7+中,opaque result types和some参数通过取消容器来减少额外开销。

如何在Swift中检查retain count?

Swift中没有直接读取retain count的API — 这被视为实现细节。针对诊断,请使用Xcode中的Instruments(Allocations、Leaks)或Memory Debugger。这些工具显示类的活实例数量和保留链。

总结

  • ARC — 通过引用计数工作的Swift和Objective-C编译器内存管理系统
  • 原理 — 每个对象都有retain count;归零时对象立即和确定性地释放
  • 与GC的区别 — ARC没有后台线程和Stop-The-World暂停,但需要控制retain cycle
  • Strong — 增加计数器;weak和unowned不增加,但unowned在释放时不会归零
  • 闭包 — Swift中retain cycle的主要原因;capture list [weak self] — 标准解决方案
  • Autorelease Pool — 循环和自定义场景中临时对象的延迟释放机制
  • 诊断 — Xcode Memory Debugger、Instruments和LeakCanary(通过ObjC bridge)用于发现问题

我们将开发一款交钥匙移动应用程序

IT Sectr自2017年以来为初创企业和企业打造iOS和Android应用程序。我们将为您提供咨询并提出最佳解决方案。

讨论项目

另请阅读