911 字
5 分钟
Swift ARC 自动引用计数
2025-03-13

Swift 中,自动引用计数(Automatic Reference Counting, ARC) 通过跟踪每个实例的引用数量,自动释放不再需要的对象内存,从而避免手动管理内存的复杂性。然而,理解 ARC 的工作原理及如何处理对象间的关系,是防止内存泄漏和构建高效应用的关键。


原理#

核心机制是:每当创建一个类实例时,系统会分配内存存储其属性及类型信息,并维护一个引用计数器。当实例被赋值给变量、常量或属性时,会建立强引用(Strong Reference),引用计数加1;当引用断开时,计数减1。当计数归零时,实例被释放。

示例

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

var person: Person? = Person(name: "张三")  // 引用计数=1
person = nil  // 引用计数归零,触发析构器

此时输出:张三被释放。若多个变量引用同一实例,需所有引用断开后才会释放:

var ref1: Person? = person
var ref2: person
ref1 = nil  // 引用计数仍为1
ref2 = nil  // 计数归零,释放实例

循环强引用#

当两个类实例互相持有对方的强引用时,会导致循环强引用(Retain Cycle),使ARC无法正确释放内存。

场景示例

class Person {
    var apartment: Apartment?
}
class Apartment {
    var tenant: Person?
}

var john: Person? = Person(name: "John")
var unit4A: Apartment? = Apartment(unit: "4A")
john!.apartment = unit4A
unit4A!.tenant = john  // 互相强引用
john = nil  // 未触发析构
unit4A = nil  // 仍未被释放

此时johnunit4A的引用计数始终为1,导致内存泄漏。


弱引用与无主引用#

Swift提供了两种方案打破循环引用:

  1. 弱引用(Weak Reference)
    适用于生命周期较短的实例。弱引用不会增加引用计数,且当目标实例释放时自动置为nil

    class Apartment {
        weak var tenant: Person?  // 弱引用
    }

    john = nil时,tenant自动置为nilApartment实例随后被释放。

  2. 无主引用(Unowned Reference)
    适用于生命周期相同或更长的实例。与弱引用不同,无主引用始终假定有值,不会自动置为nil

    class CreditCard {
        unowned let customer: Customer  // 无主引用
    }
    class Customer {
        var card: CreditCard?
    }

    CreditCard实例的生命周期不会超过关联的Customer,因此可安全使用无主引用。


闭包中的循环引用与捕获列表#

闭包作为引用类型,若在内部捕获self,也可能导致循环引用。例如:

class HTMLElement {
    lazy var asHTML: () -> String = {
        return "<\(self.name)>\(self.text ?? "")</\(self.name)>"
    }
    // ...
}

此时闭包持有self的强引用,而self也通过属性持有闭包,形成循环。

解决方案:使用捕获列表声明unownedweak引用:

lazy var asHTML: () -> String = { [unowned self] in
    return "<\(self.name)>\(self.text ?? "")</\(self.name)>"
}

通过[unowned self],闭包不再强持有self,打破循环。


特殊场景:隐式解包可选与无主引用#

在需要确保初始化顺序的场景中,可结合隐式解包可选类型无主引用

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

capitalCityCountry初始化完成后才被赋值,但通过隐式解包确保使用时非空,避免循环。


局限与最佳实践#

  • 仅适用于类:结构体和枚举是值类型,不参与引用计数。
  • 避免过度使用unowned:错误使用可能导致运行时崩溃。
  • 调试工具:利用Xcode的Memory Graph Debugger检测循环引用。
Swift ARC 自动引用计数
https://blog.lpkt.cn/posts/swift-arc/
作者
lollipopkit
发布于
2025-03-13
许可协议
CC BY-NC-SA 4.0