Swift

自动引用计数

本文详细介绍了 Swift 中的自动引用计数(ARC)机制,包括其工作原理、循环引用问题及其解决方案(弱引用和无主引用),以及闭包中的循环引用。

Swift 使用自动引用计数(ARC)机制来跟踪和管理应用程序的内存。在大多数情况下,这意味着内存管理在 Swift 中是“这就行了”的,你不需要自己考虑内存管理。当实例不再被需要时,ARC 会自动释放类实例所占用的内存。

ARC 的工作原理

每当你创建一个类的新实例时,ARC 会分配一块内存来存储该实例的信息。当实例不再被需要时,ARC 会释放该内存。为了确保实例在仍被使用时不会消失,ARC 会跟踪当前引用每个类实例的属性、常量和变量的数量。只要存在至少一个有效引用,ARC 就不会释放该实例。

类实例之间的循环引用

如果两个类实例互相持有对方的强引用,就会发生循环引用,导致内存无法被释放。

class Person {
    let name: String
    init(name: String) { self.name = name }
    var apartment: Apartment?
    deinit { print("\(name) is being deinitialized") }
}

class Apartment {
    let unit: String
    init(unit: String) { self.unit = unit }
    var tenant: Person?
    deinit { print("Apartment \(unit) is being deinitialized") }
}

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

john!.apartment = unit4A
unit4A!.tenant = john

john = nil
unit4A = nil
// 此时两个实例都无法被释放,因为它们互相强引用

解决循环引用

Swift 提供了两种方式来解决类实例之间的循环引用:弱引用(weak)和无主引用(unowned)。

弱引用 (Weak References)

弱引用不会保持其引用的实例,因此不会阻止 ARC 释放该实例。弱引用必须声明为可选类型的变量,因为引用的实例可能会被释放。

class Apartment {
    let unit: String
    init(unit: String) { self.unit = unit }
    weak var tenant: Person? // 使用 weak 打破循环引用
    deinit { print("Apartment \(unit) is being deinitialized") }
}

无主引用 (Unowned References)

和弱引用类似,无主引用也不会保持其引用的实例。不同的是,无主引用假定其引用的实例始终存在(非可选类型)。

class Customer {
    let name: String
    var card: CreditCard?
    init(name: String) { self.name = name }
}

class CreditCard {
    let number: UInt64
    unowned let customer: Customer // 使用 unowned,因为信用卡必须关联一个客户
    init(number: UInt64, customer: Customer) {
        self.number = number
        self.customer = customer
    }
}

闭包的循环引用

当闭包捕获了 self,并且该闭包又被赋值给了 self 的某个属性时,也会发生循环引用。解决方法是使用捕获列表(Capture List)。

class HTMLElement {
    let name: String
    let text: String?
    
    lazy var asHTML: () -> String = { [unowned self] in // 捕获列表
        if let text = self.text {
            return "<\(self.name)>\(text)</\(self.name)>"
        } else {
            return "<\(self.name) />"
        }
    }
    
    init(name: String, text: String? = nil) {
        self.name = name
        self.text = text
    }
}

总结

  • Strong:默认引用类型,保持对象存活。
  • Weak:不保持对象存活,必须是可选类型,对象释放后自动变为 nil
  • Unowned:不保持对象存活,必须是非可选类型,访问已释放对象会崩溃。
  • Capture List:用于打破闭包和类实例之间的循环引用。
在 GitHub 上编辑

上次更新于