Swift 5.x - 自动引用计数(中文文档)

699 阅读26分钟

引言

继续学习Swift文档,从上一章节:不透明类型,我们学习了Swift不透明类型的内容,主要有具有不透明返回类型的函数或方法隐藏其返回值的类型信息。不是提供一个具体的类型作为函数的返回类型,而是根据它支持的协议来描述返回值。现在,我们学习Swift自动引用计数的相关内容。由于篇幅较长,这里分篇来记录,接下来,开始吧!

自动引用计数

Swift使用自动引用计数(ARC)来跟踪和管理应用程序的内存使用情况。在大多数情况下,这意味着内存管理在Swift中“只起作用”,而且您不需要自己考虑内存管理。当不再需要类实例时,ARC会自动释放这些实例所使用的内存。

但是,在少数情况下,ARC需要更多关于代码各部分之间关系的信息,以便为您管理内存。本章将描述这些情况,并说明如何启用ARC来管理应用程序的所有内存。在Swift中使用ARC与在Objective-C中使用ARC的方法非常相似。

引用计数仅适用于类的实例。结构体和枚举是值类型,而不是引用类型,并且不是通过引用存储和传递的。

1 ARC如何工作的

每次创建类的新实例时,ARC都会分配一块内存来存储有关该实例的信息。此内存保存有关实例类型的信息,以及与该实例关联的任何存储属性的值。

此外,当不再需要某个实例时,ARC会释放该实例使用的内存,以便该内存可以用于其他用途。这样可以确保类实例在不再需要时不会占用内存中的空间。

但是,如果ARC释放一个仍在使用的实例,则无法再访问该实例的属性或调用该实例的方法。实际上,如果你试图访问实例,你的应用程序很可能会崩溃。

为了确保实例在仍然需要时不会消失,ARC跟踪当前引用每个类实例的属性、常量和变量的数量。只要实例至少有一个活动引用仍然存在,ARC就不会释放该实例。

为了实现这一点,无论何时将类实例分配给属性、常量或变量,该属性、常量或变量都会对该实例进行强引用。这个引用被称为“强”引用,因为它保持对该实例的坚定控制,并且只要强引用仍然存在,它就不允许被释放。

2 ARC实践

下面是一个自动引用计数工作原理的示例。此示例从一个名为Person的简单类开始,该类定义了一个名为name的存储常量属性:

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

Person类有一个initializer,它设置实例的name属性,并打印一条消息来指示初始化正在进行中。Person类还有一个deinitializer ,当类的实例被释放时,它会打印一条消息。

下一个代码片段定义了Person类型的三个变量?,用于在后续代码段中设置对新Person实例的多个引用。因为这些变量是可选类型(Person?,而不是Person),它们被自动初始化为nil值,并且当前不引用Person实例。

var reference1: Person?
var reference2: Person?
var reference3: Person?

现在可以创建一个新的Person实例并将其分配给以下三个变量之一:

reference1 = Person(name: "John Appleseed")
// Prints "John Appleseed is being initialized"

注意,消息“johnappleseed正在被初始化”是在调用Person类的初始值设定项时打印的。这将确认初始化已发生。 因为新的Person实例已经分配给reference1变量,所以现在reference1对新Person实例有一个强引用。因为至少有一个强引用,ARC确保此Person被保存在内存中,并且不会被释放。

如果将同一个人实例分配给另外两个变量,则会建立对该实例的两个更强的引用:

reference2 = reference1
reference3 = reference1

现在有三个对这个Person实例的强引用。

如果通过将nil分配给两个变量来中断其中两个强引用(包括原始引用),则将保留一个强引用,并且不会释放Person实例:

reference1 = nil
reference2 = nil

ARC不会释放Person实例,直到第三个也是最后一个强引用被破坏,此时很明显您不再使用Person实例:

reference3 = nil
// Prints "John Appleseed is being deinitialized"

3 类实例之间的强引用循环

在上面的示例中,ARC能够跟踪对您创建的新Person实例的引用数量,并在不再需要该实例时取消分配该实例。

但是,可以编写这样的代码:类的实例永远不会达到它没有强引用的程度。如果两个类实例彼此具有强引用,从而使每个实例保持另一个实例的活动状态,则可能会发生这种情况。这就是所谓的强引用循环。

通过将类之间的某些关系定义为弱引用或无主引用而不是强引用,可以解决强引用循环。在解析类实例之间的强引用循环中描述了这个过程。然而,在学习如何解析强引用循环之前,了解这种循环是如何产生的是很有用的。

下面是一个例子,说明强引用循环是如何意外产生的。此示例定义了两个名为“Person”和“Apartment”的类,它们对Apartment及其住户进行建模:

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") }
}

每个Person实例都有一个String类型的name属性和一个初始为nil的可选Apartment属性。Apartment属性是可选的,因为一个人可能并不总是有一个Apartment。

类似地,每个Apartment实例都有一个String类型的unit属性,并且有一个初始值为nil的可选tenant属性。租户属性是可选的,因为公寓可能并不总是有租户。

这两个类还定义了一个deinitializer,它打印出该类的一个实例正在被销毁的情况。这使您能够查看Person和Apartment实例是否按预期被释放。

下一个代码片段定义了两个可选类型的变量john和unit4A,它们将被设置为下面的一个特定的Apartment和Person实例。由于可选,这两个变量的初始值都为零:

var john: Person?
var unit4A: Apartment?

现在可以创建一个特定的Person实例和Apartment实例,并将这些新实例分配给john和unit4A变量:

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

下面是创建和分配这两个实例后强引用的情况。john变量现在具有对新Person实例的强引用,unit4A变量具有对新Apartment实例的强引用:

现在可以将这两个实例链接在一起,以便此人有一个公寓,而公寓有一个租户。注意一个感叹号(!)用于展开和访问存储在john和unit4A可选变量中的实例,以便可以设置这些实例的属性:

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

以下是将两个实例链接在一起后强引用的情况:

不幸的是,链接这两个实例在它们之间创建了一个强引用循环。Person实例现在具有对Apartment实例的强引用,而Apartment实例具有对Person实例的强引用。因此,当您中断john和unit4A变量持有的强引用时,引用计数不会降到零,并且实例不会通过ARC释放:

john = nil
unit4A = nil

请注意,当您将这两个变量设置为nil时,这两个deinitializer都没有被调用。强引用循环阻止Person和Apartment实例被释放,从而导致应用程序内存泄漏。

以下是将john和unit4A变量设置为nil后强引用的情况:

Person实例和Apartment实例之间的强引用仍然存在,并且不能被破坏。

4 解决类实例之间的强引用循环

当使用类类型的属性时,Swift提供了两种解决强引用循环的方法:弱引用和unowned引用。

弱引用和unowned引用使引用循环中的一个实例能够引用另一个实例,而无需对其进行强保持。然后,实例可以相互引用,而不会产生强引用循环。

当另一个实例的生存期较短时,即可以先释放另一个实例时,请使用弱引用。在上面的公寓示例中,对于一个公寓来说,在其生命周期中的某个时刻能够没有租户是合适的,因此在这种情况下,弱引用是打破引用循环的一种适当方法。相反,当另一个实例具有相同的生存期或更长的生存期时,请使用unowned引用。

4.1 弱引用

弱引用是指对其引用的实例不保持强保持,因此不会阻止ARC处理被引用实例。此行为可防止引用成为强引用循环的一部分。通过在属性或变量声明之前放置weak关键字来指示弱引用。

因为弱引用不会对它所引用的实例保持强挂起,所以当弱引用仍在引用它时,可能会释放该实例。因此,当ARC引用的实例被释放时,ARC会自动将弱引用设置为nil。而且,由于弱引用需要允许在运行时将它们的值更改为nil,所以它们总是声明为可选类型的变量,而不是常量。

您可以检查弱引用中是否存在一个值,就像任何其他可选值一样,您永远不会得到对不再存在的无效实例的引用。

注意
当ARC将弱引用设置为nil时,不调用属性观察器。

下面的示例与上面的“Person和Apartment”示例相同,但有一个重要区别。这一次,Apartment类型的租户属性被声明为弱引用:

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 }
    weak var tenant: Person?
    deinit { print("Apartment \(unit) is being deinitialized") }
}

来自两个变量(john和unit4A)的强引用以及两个实例之间的链接如前所示:

var john: Person?
var unit4A: Apartment?

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

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

下面是将两个实例链接在一起后引用的情况:

Person实例仍然具有对Apartment实例的强引用,但Apartment实例现在具有对Person实例的弱引用。这意味着,当您通过将john变量设置为nil来中断它所持有的强引用时,将不再有对Person实例的强引用:

john = nil
// Prints "John Appleseed is being deinitialized"

因为对Person实例没有更多的强引用,因此它被释放,tenant属性设置为nil:

对Apartment实例的唯一剩余强引用来自unit4A变量。如果断开该强引用,则不再有对Apartment实例的强引用:

unit4A = nil
// Prints "Apartment 4A is being deinitialized"

因为不再有对Apartment实例的强引用,因此也会释放该实例:

注意
在使用垃圾收集的系统中,弱指针有时用于实现简单的缓存机制,因为只有当内存压力触发垃圾回收时,才释放没有强引用的对象。但是,对于ARC,一旦删除最后一个强引用,就会释放值,使弱引用不适合这样做。

4.2 Unowned引用

与弱引用一样,Unowned引用不会对其引用的实例保持强保持。但是,与弱引用不同,当另一个实例具有相同的生存期或更长的生存期时,将使用Unowned引用。通过在属性或变量声明之前放置unowned关键字,可以指示Unowned引用。 与弱引用不同,Unowned引用应始终具有值。因此,将一个值标记为unowned并不能使它成为可选的,ARC从不将unowned引用的值设置为nil。

重点
只有在确定引用始终引用尚未释放的实例时,才使用Unowned引用。 如果在解除分配一个Unowned引用后尝试访问该引用的值,则会出现运行时错误。

下面的示例定义了两个类,Customer和CreditCard,它们为银行客户和该客户可能的信用卡建模。这两个类各自存储另一个类的实例作为属性。这种关系有可能产生一个强大的参考循环。

客户和信用卡之间的关系与上面弱参考示例中看到的公寓和人之间的关系略有不同。在这个数据模型中,客户可能有信用卡,也可能没有信用卡,但信用卡始终与客户关联。信用卡实例的寿命永远不会超过它所引用的客户。为了表示这一点,Customer类有一个可选的card属性,但是CreditCard类有一个Unowned(非可选)的Customer属性。

此外,新的信用卡实例只能通过向自定义信用卡初始值设定项传递数字值和客户实例来创建。这可以确保在创建信用卡实例时,信用卡实例始终具有与其关联的客户实例。

由于信用卡始终有客户,因此您将其客户属性定义为Unowned引用,以避免强引用循环:

class Customer {
    let name: String
    var card: CreditCard?
    init(name: String) {
        self.name = name
    }
    deinit { print("\(name) is being deinitialized") }
}

class CreditCard {
    let number: UInt64
    unowned let customer: Customer
    init(number: UInt64, customer: Customer) {
        self.number = number
        self.customer = customer
    }
    deinit { print("Card #\(number) is being deinitialized") }
}

注意
CreditCard类的number属性定义为UInt64类型,而不是Int类型,以确保number属性的容量足够大,可以在32位和64位系统上存储16位的卡号。

下一个代码片段定义了一个可选的客户变量john,它将用于存储对特定客户的引用。此变量的初始值为零,因为它是可选的:

var john: Customer?

现在,您可以创建一个客户实例,并使用它初始化和分配一个新的信用卡实例作为该客户的card属性:

john = Customer(name: "John Appleseed")
john!.card = CreditCard(number: 1234_5678_9012_3456, customer: john!)

下面是引用的情况,现在您已经链接了两个实例:

Customer实例现在具有对CreditCard实例的强引用,而CreditCard实例具有对Customer实例的Unowned引用。

由于Unowned客户引用,当您断开john变量持有的强引用时,将不再有对客户实例的强引用:

因为不再有对客户实例的强引用,所以它被释放了。在这种情况下,不再有对CreditCard实例的强引用,它也被释放:

john = nil
// Prints "John Appleseed is being deinitialized"
// Prints "Card #1234567890123456 is being deinitialized"

上面最后的代码片段显示,Customer实例和CreditCard实例的deinitializers都会在john变量设置为nil之后打印它们的“deinitialized”消息。

注意
上面的例子展示了如何使用安全的Unowned引用。Swift还为需要禁用运行时安全检查(例如,出于性能原因)的情况提供不安全的Unowned引用。与所有不安全的操作一样,您负责检查代码的安全性。 您可以通过编写unowned(不安全)来指示不安全的unowned引用。如果在释放引用的实例后尝试访问不安全的Unowned引用,则程序将尝试访问该实例以前所在的内存位置,这是不安全的操作。

4.3 Unowned可选引用

可以将对类的可选引用标记为unowned。在ARC所有权模型中,unowned可选引用和弱引用可以在相同的上下文中使用。不同的是,当您使用一个unowned的可选引用时,您要负责确保它始终引用一个有效的对象或设置为nil。

以下是一个跟踪学校特定部门提供的课程的示例:

class Department {
    var name: String
    var courses: [Course]
    init(name: String) {
        self.name = name
        self.courses = []
    }
}

class Course {
    var name: String
    unowned var department: Department
    unowned var nextCourse: Course?
    init(name: String, in department: Department) {
        self.name = name
        self.department = department
        self.nextCourse = nil
    }
}

该系对该系提供的每门课程都有很强的参考价值。在ARC所有权模型中,一个系拥有它的课程。Course有两个unowned引用,一个指向系,一个指向学生应该修的下一个课程;一个课程不拥有这两个对象。每门课程都是某个系的一部分,因此department属性不是可选的。但是,由于某些课程没有推荐的后续课程,nextCourse属性是可选的。

下面是使用这些类的示例:

let department = Department(name: "Horticulture")

let intro = Course(name: "Survey of Plants", in: department)
let intermediate = Course(name: "Growing Common Herbs", in: department)
let advanced = Course(name: "Caring for Tropical Plants", in: department)

intro.nextCourse = intermediate
intermediate.nextCourse = advanced
department.courses = [intro, intermediate, advanced]

上面的代码创建了一个系及其三门课程。入门课程和中级课程都在其nextCourse属性中存储了一个建议的下一个课程,该属性保留了一个对学生完成本课程后应修课程的unowned可选引用。

一个unowned的可选引用不会对它包装的类的实例保持一个强的控制,因此它不会阻止ARC释放实例。它的行为与ARC下的unowned引用的行为相同,只是unowned可选引用可以为nil。

与非可选的unowned引用一样,您有责任确保nextCourse始终引用尚未取消分配的课程。在这种情况下,例如,当您从系课程您还需要删除其他课程可能对它的任何引用。

注意
可选值的基础类型是可选的,它是Swift标准库中的枚举。但是,optionals是一个例外,即值类型不能标记为unowned。 封装类的optional不使用引用计数,因此不需要维护对optional的强引用。

4.4 unowned引用和隐式展开的可选属性

上面的弱引用和unowned引用的示例涵盖了两种更常见的场景,在这些场景中,必须打破强引用循环。

Person和Apartment示例显示了这样一种情况:允许两个属性为nil,这两个属性都有可能导致强引用循环。这种情况最好用弱引用来解决。

Customer和CreditCard示例显示了这样一种情况:一个允许为nil的属性和另一个不能为nil的属性有可能导致强引用循环。这种情况最好使用unowned引用来解决。

但是,还有第三种情况,在这种情况下,两个属性都应该始终具有值,并且一旦初始化完成,两个属性都不应该为nil。在这种情况下,将一个类上的unowned属性与另一个类上隐式展开的可选属性结合起来是很有用的。

这样可以在初始化完成后直接访问这两个属性(无需可选的展开),同时仍然避免引用循环。本节向您展示如何建立这种关系。

下面的示例定义了两个类,Country和City,每个类都将另一个类的实例存储为属性。在这个数据模型中,每个国家都必须有一个首都,每个城市必须始终属于一个国家。为了表示这一点,Country类具有capitalCity属性,City类具有Country属性:

class Country {
    let name: String
    var capitalCity: City!
    init(name: String, capitalName: String) {
        self.name = name
        self.capitalCity = City(name: capitalName, country: self)
    }
}

class City {
    let name: String
    unowned let country: Country
    init(name: String, country: Country) {
        self.name = name
        self.country = country
    }
}

为了在两个类之间建立相互依赖关系,City的初始值设定项获取一个Country实例,并将该实例存储在其Country属性中。

City的初始值设定项从Country的初始值设定项中调用。但是,在新的Country实例完全初始化之前,Country的初始值设定项不能将self传递给City初始值设定项,如两阶段初始化中所述。

为了满足这一要求,您将Country的capitalCity属性声明为隐式展开的可选属性,由其类型注释(City!)末尾的感叹号表示。这意味着capitalCity属性的默认值为nil,与任何其他可选属性一样,但是可以访问它,而无需像隐式Unwrapped Optionals中描述的那样展开其值。

因为capitalCity有一个默认的nil值,所以只要Country实例在其初始值设定项中设置了name属性,新的Country实例就被视为已完全初始化。这意味着一旦name属性被设置,Country初始值设定项就可以开始引用并传递隐式self属性。因此,当国家/地区初始值设定项设置自己的capitalCity属性时,国家/地区初始值设定项可以将self作为城市初始值设定项的参数之一传递。

所有这些都意味着您可以在单个语句中创建Country和City实例,而无需创建强引用循环,并且可以直接访问capitalCity属性,而无需使用感叹号来展开其可选值:

var country = Country(name: "Canada", capitalName: "Ottawa")
print("\(country.name)'s capital city is called \(country.capitalCity.name)")
// Prints "Canada's capital city is called Ottawa"

在上面的示例中,使用隐式解包的可选项意味着满足所有两阶段类初始值设定项要求。一旦初始化完成,就可以像非可选值一样使用和访问capitalCity属性,同时仍然可以避免强引用循环。

5 闭包的循环引用

您在上面看到了当两个类实例属性彼此持有强引用时如何产生强引用循环。您还了解了如何使用弱引用和unowned引用来打破这些强引用循环。

如果为类实例的属性分配闭包,并且该闭包的主体捕获该实例,则也可能发生强引用循环。这种捕获可能是因为闭包的主体访问实例的属性,例如self。一些特性,或者因为闭包调用实例上的方法,例如self.someMethod(). 无论哪种情况,这些访问都会导致闭包“捕获”self,从而创建一个强引用循环。

这种强引用循环的发生是因为闭包和类一样都是引用类型。将闭包指定给属性时,就是为该闭包指定引用。从本质上讲,这是同一个问题,因为上面两个强引用使彼此保持活力。然而,这次不是两个类实例,而是一个类实例和一个闭包,它们保持着彼此的生命周期。

Swift为这个问题提供了一个优雅的解决方案,称为闭包捕获列表。然而,在学习如何用闭包捕获列表打破强引用循环之前,了解如何导致这种循环是很有用的。

下面的示例演示如何在使用引用self的闭包时创建强引用循环。此示例定义了一个名为HTMLElement的类,它为HTML文档中的单个元素提供了一个简单的模型:

class HTMLElement {

    let name: String
    let text: String?

    lazy var asHTML: () -> String = {
        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
    }

    deinit {
        print("\(name) is being deinitialized")
    }

}

HTMLElement类定义一个name属性,该属性指示元素的名称,例如“h1”表示heading元素,“p”表示段落元素,或“br”表示换行元素。HTMLElement还定义了一个可选的文本属性,您可以将其设置为一个字符串,该字符串表示要在该HTML元素中呈现的文本。

除了这两个简单的属性之外,HTMLElement类还定义了一个名为asHTML的惰性属性。此属性引用一个将名称和文本合并为HTML字符串片段的闭包。asHTML属性的类型是()->String,或者“一个不带参数并返回一个字符串值的函数”。

默认情况下,asHTML属性被分配一个闭包,该闭包返回HTML标记的字符串表示形式。如果文本不存在,则返回可选文本。对于段落元素,闭包将返回“

some text

”或“

”,这取决于text属性是等于“some text”还是nil。

asHTML属性的命名和使用有点像实例方法。但是,由于asHTML是一个闭包属性而不是一个实例方法,所以如果您想更改特定HTML元素的HTML呈现,可以用自定义闭包替换asHTML属性的默认值。

例如,可以将asHTML属性设置为一个闭包,如果text属性为nil,则默认为某些文本,以防止表示返回空的HTML标记:

let heading = HTMLElement(name: "h1")
let defaultText = "some default text"
heading.asHTML = {
    return "<\(heading.name)>\(heading.text ?? defaultText)</\(heading.name)>"
}
print(heading.asHTML())
// Prints "<h1>some default text</h1>"

注意
asHTML属性声明为lazy属性,因为只有当元素实际需要呈现为某个HTML输出目标的字符串值时才需要它。asHTML是一个lazy属性,这意味着您可以在默认闭包中引用self,因为只有在初始化完成并且self已知存在之后,才能访问lazy属性。

HTMLElement类提供了一个初始化器,它接受name参数和(如果需要)文本参数来初始化新元素。该类还定义了一个deinitializer,它在释放HTMLElement实例时打印一条消息以显示。

下面是如何使用HTMLElement类创建和打印新实例:

var paragraph: HTMLElement? = HTMLElement(name: "p", text: "hello, world")
print(paragraph!.asHTML())
// Prints "<p>hello, world</p>"

注意
上面的paragraph变量被定义为一个可选的HTMLElement,因此可以在下面将其设置为nil,以演示强引用循环的存在。

不幸的是,如上所述,HTMLElement类在HTMLElement实例和用于其默认asHTML值的闭包之间创建了一个强引用循环。以下是循环的情况:

实例的asHTML属性包含对其闭包的强引用。但是,因为闭包指的是它身体内的self(作为一种参照的方式)self.name以及self.text),闭包捕获self,这意味着它保留对HTMLElement实例的强引用。两者之间形成了一个强引用循环。(有关在闭包中捕获值的更多信息,请参见捕获值。)

注意
即使闭包多次引用self,它也只捕获对HTMLElement实例的一个强引用。

如果将paragraph变量设置为nil并中断其对HTMLElement实例的强引用,则不会释放HTMLElement实例及其闭包,因为强引用循环:

paragraph = nil

请注意,HTMLElement deinitializer中的消息未被打印,这表明HTMLElement实例未被释放。

6 解决闭包的循环引用

通过将捕获列表定义为闭包定义的一部分,可以解决闭包和类实例之间的强引用循环。捕获列表定义捕获闭包体中的一个或多个引用类型时要使用的规则。与两个类实例之间的强引用循环一样,您将每个捕获的引用声明为弱引用或unowned引用,而不是强引用。弱或unowned的适当选择取决于代码不同部分之间的关系。

注意
swift要求你写self.someProperty or self.someMethod()(而不仅仅是someProperty或someMethod())每当您在闭包中引用self的成员时。这有助于你记住,偶然捕捉self是可能的。

6.1 定义捕获列表

捕获列表中的每个项都是弱关键字或unowned关键字与类实例(如self)的引用或用某个值初始化的变量(如delegate = self.delegate). 这些对写在一对方括号内,用逗号隔开。

将捕获列表放在闭包的参数列表和返回类型(如果提供)之前:

lazy var someClosure = {
    [unowned self, weak delegate = self.delegate]
    (index: Int, stringToProcess: String) -> String in
    // closure body goes here
}

如果闭包没有指定参数列表或返回类型,因为它们可以从上下文中推断出来,请将捕获列表放在闭包的最开始,后跟in关键字:

lazy var someClosure = {
    [unowned self, weak delegate = self.delegate] in
    // closure body goes here
}
6.2 Weak和Unowned引用

将闭包中的捕获定义为unowned引用,此时闭包和它捕获的实例将始终相互引用,并始终在同一时间释放。

相反,当捕获的引用在将来的某个时刻可能变为nil时,将捕获定义为弱引用。弱引用始终是可选类型,并且在释放它们引用的实例时自动变为nil。这使您能够检查它们是否存在于闭包的主体中。

注意
如果捕获的引用永远不会变为nil,则应始终将其捕获为unowned引用,而不是弱引用。

unowned reference是用于从上面闭包的强引用循环中解析HTMLElement示例中的强引用循环的适当捕获方法。以下是如何编写HTMLElement类以避免循环:

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
    }

    deinit {
        print("\(name) is being deinitialized")
    }

}

HTMLElement的这个实现与前面的实现相同,只是在asHTML闭包中添加了一个捕获列表。在本例中,捕获列表是[unowned self],这意味着“将self捕获为一个unowned引用,而不是一个强引用”。

您可以像以前一样创建和打印HTMLElement实例:

var paragraph: HTMLElement? = HTMLElement(name: "p", text: "hello, world")
print(paragraph!.asHTML())
// Prints "<p>hello, world</p>"

下面是在捕获列表就位后引用的情况:

这一次,闭包对self的捕获是一个unowned引用,并且不会对它捕获的HTMLElement实例保持强引用。如果将paragraph变量的强引用设置为nil,则HTMLElement实例将被释放,如下例中从其取消初始化器消息的打印中可以看到:

paragraph = nil
// Prints "p is being deinitialized"

有关捕获列表的详细信息,请参见捕获列表

参考文档: Swift - Automatic Reference Counting