自动引用计数

未匹配的标注

Swift 使用 自动引用计数( ARC ) 来跟踪和管理你的应用程序的内存使用情况。通常情况下,Swift 的内存管理机制会一直起着「作用」,而且你也无须考虑自己管理内存。ARC 会自动帮你释放那些不再使用了的实例所占的内存空间。

然而,在一些特殊情况下,ARC 需要多一些你代码之间的引用关系,才能帮你正确管理内存。本章主要是就这些情况来描述该如何使用 ARC 来管理你的应用程序的内存。在 Swift 中使用 ARC 和在 Objective-C 中使用 ARC 非常相似,可以参考后者书籍「迁移到 ARC 使用说明」 。

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

ARC 工作机制

当你每次创建一个类的新的实例时,ARC 会分配一块内存来存储这个实例信息。内存会包含该实例的类型信息,以及该实例所有相关的存储属性的值。

此外,当该实例不再使用时,ARC 会释放该实例所占的内存空间,以便内存回收使用。这就确保了不再被使用的实例不会一直占用内存空间。

然而,ARC 释放了的实例是不能再被访问属性或是调用方法的。实际上,如果你试图访问该实例,那你的应用程序很可能会崩溃。

为了确保实例正在使用的时候不会被释放,ARC 会跟踪记录每一个实例被属性、常量和变量所引用的次数。只要实例还被有效的引用着,ARC 就不会释放该实例。

为了实现这一点,在实例被分配给属性、常量或是变量的时候,这个属性、常量或是变量就会对该实例产生 强引用,这种引用之所以被称之为「 强 」引用,是因为它保持了对这个实例牢固的拥有关系,只要该强引用还存在,就不允许释放该实例。


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 类有个用于设置 name 属性的构造函数,该函数会打印一条信息以表明构造函数生效。Person 类还有个析构函数,该函数会在实例被销毁时打印一条信息。

接下来的代码定义了三个 Person? 类型的变量,这三个变量会在后面的代码中设置,对一个 Person 实例建立多个强引用关系。因为这三个变量是可选类型的( Person? 而不是 Person ),因此,它们会被自动初始化为 nil,目前还不会引用到任何 Person 实例。

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

现在创建一个 Person 实例,并将其赋给其中一个变量:

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

然后你会注意到,在你调用 Person 构造函数时,会打印出信息 「John Appleseed is being initialized」 。由此确定构造函数被成功执行。

由于新创建的 Person 实例被赋值给了变量 reference1,所以变量 reference1 会对 Person 实例持有强引用。由于至少有一个强引用存在,所以 ARC 会保证 Person 实例保留在内存中且不被释放。

如果你把同一个 Person 实例赋给另外两个变量,则该实例又会多出两个强引用:

reference2 = reference1
reference3 = reference1

现在有 三个 强引用指向同一个 Person 实例。

如果你把两个变量设置为 nil,就断开了这两个强引用(包括原来的引用),但仍然保留着一个强引用,因此这个 Person 实例不会被释放:

reference1 = nil
reference2 = nil

ARC 会在第三个即最后一个强引用断开时释放 Person 实例,这也意味着你不再使用这个 Person 实例了:

reference3 = nil
// 打印 "John Appleseed is being deinitialized"


实例之间的强引用循环

在上面的例子中,ARC 能够跟踪新创建的 Person 实例的引用次数,并且在 Person 实例不再需要时将其释放。

然而,我们是能够编写出这样的代码:一个类的实例 永远 都不会没有强引用。这种情况可能发生在两个实例互相强引用对方,因此这俩实例都使对方存活且不被释放。这种被称之为 强引用循环

你可以通过将实例之间的某些关系定义为弱引用(weak)或无主引用(unowned)而不是强引用来解决强引用循环问题。具体过程在「解决实例之间的强引用循环」中有描述。然而,在你知道学习怎样解决强循环引用之前,很有必要了解这种循环是如何产生的。

下面一个示例讲述如何不经意的产生强循环引用。这个例子定义了两个类:Person 和 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 实例都有一个名为 name 的 String 类型属性和一个初始化为 nil 的名为 apartment 的可选类型属性。apartment 属性是可选的,因为并不是每一个人都拥有一栋公寓。

类似的,每一个 Apartment 实例都有一个名为 unit 的 String 类型属性和一个初始化为 nil 的名为 tenant 的可选类型属性。tenant 属性是可选的,因为并不是每一栋公寓都有住户。

这些类都定义了析构函数,用来在实例析构的时候打印信息。这能够让你清楚的看到 Person 和 Apartment 的实例是否像预期那样被释放。

下面的代码定义了两个叫 john 和 unit4A 的可选类型的变量,然后将它们设置为下面的 Apartment 和 Person 的实例。由于是可选的,这两个变量初始化都是 nil

var john: Person?
var unit4A: Apartment?

现在创建一个 PersonApartment 的特定的实例,然后分别赋给 john 和 unit4A 变量。

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

创建和赋值这两个实例之后,强引用关系如下图。现在变量 john 持有指向新创建的 Person 实例的强引用,变量 unit4A 持有指向新创建的  Apartment 实例的强引用:

0

现在你可以把这两个实例关联起来,这样人就能有公寓住了,而公寓也有了住户。注意感叹号( ! )是用来解包并访问可选变量 john 和 unit4A 所存储的实例,这样实例的属性才能被赋值: 

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

将这两个实例关联之后,强引用关系如下图:

0

遗憾的是将这两个对象关联起来之后反而产生了强引用循环。Person 实例持有指向 Apartment 实例的强引用,而 Apartment 实例也持有指向 Person 实例的强引用。因此,当你断开变量 john 和 unit4A 的所持有的强引用的时候,引用计数仍不会降到0,实例也不会被 ARC 释放:

john = nil
unit4A = nil

那你会注意到当你设置这俩变量为 nil 的时候,没有一个析构函数被调用。强引用循环阻止了 Person 和 Apartment 实例释放回收,并在你的应用程序中产生内存泄露。

将变量 john 和 unit4A 设置为 nil 之后,强引用关系如下图:

0

Person 实例和 Apartment 实例之间的强引用关系仍然保留了下来,并且不会被打断。


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

Swift 提供了两种方法解决你在使用类的属性而产生的强引用循环:弱引用( weak )和无主引用( unowned )。
弱引用( weak )和无主引用( unowned )能确保一个实例在循环引用中引用另一个实例,而 不用 保持强引用关系。这样实例就可以相互引用且不会产生强引用循环。

当一个实例的生命周期比较引用它的实例短,也就是这个实例可能会先于引用它的实例释放的时候,需要使用弱引用( weak )。对与一栋公寓来说在它的生命周期中是完全可以没有住户的,所以在这种情况下,上例中 Apartment 类使用弱引用来打断强引用循环是合适的。相反,当一个实例拥有和引用它的实例相同的生命周期或是比引用它的实例更长的生命周期的时候,需要使用无主引用( unowned )。

弱引用

弱引用 不会强持有引用的实例,并且不会阻止 ARC 销毁引用的实例。这可以避免强引用循环。属性或是变量声明的前面加上 weak 关键词来表示这是弱引用。

由于弱引用不会强持有引用的实例,所以即使弱引用存在这个实例也可能被释放。因此,当弱引用的实例释放的时候,ARC 会自动将其设置为 nil。由于弱引用需要能在运行过程中设置为 nil ,所以必需要声明为可选类型的变量而不是常量。

你可以像其他可选值一样检查弱引用的值是否存在,并且你永远不会遇到一个不存在的无效实例的引用。

注意

当 ARC 设置弱引用为 nil 的时候,属性观察不会被调用。

下面的例子跟上面的 Person 和 Apartment 的例子相同,但只有一个重要的区别。这一次,Apartment 的 tenant 属性被声明为弱引用(weak)。

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

现在是将这两个实例变量关联在一起之后,引用关系图如下:

0

Person 实例依然持有一个对 Apartment 实例的强引用,但现在 Apartment 实例持有一个对 Person 的 引用。这意味着将变量 john 设置为nil,就会打断这个强引用,也就没有任何指向Person 实例的强引用了:

john = nil
// 打印 "John Appleseed is being deinitialized"

现在因为再也没有指向 Person 实例的强引用了,所有它被释放了,属性 tenant 也会被设置为 nil

0

现在变量 unit4A 仍然持有一个对 Apartment 实例的强引用。如果你断开 这个 强引用,那就再也没有指向 Apartment 实例的强引用了:

unit4A = nil
// 打印 "Apartment 4A is being deinitialized"

由于再也没有指向 Apartment 实例的强引用,所以他被释放了:

0

注意

在一些使用垃圾回收机制的系统中,弱指针有时候会被用来实现简单的缓存机制,因为只有在内存压力触发回收机制时,才会释放这些没有强引用的对象。但是,使用 ARC 时,只要最后一个强引用被断开,值就会被释放,所以,弱引用不适合此类目的。

无主引用

像弱引用一样,无主引用 也不会对指向的对象持有强引用。但是,与弱引用不同的是,无主引用适用于其他实例有相同的生命周期或是更长的生命周期的场景。属性或是变量声明前面加上 unowned 关键字表示这是无主引用。

无主引用总是有值的。因而,ARC也不会将无主引用的值设置为 nil,这也意味着无主引用要被定义为非可选类型。

重要

只有在确保引用的实例 永远 不会释放,才能使用无主引用。

如果你在无主引用的对象释放之后,视图访问该值,会触发运行时错误

接下来的例子定义了两个类:Customer 和 CreditCard ,模拟了银行客户和客户的信用卡。这两个类都将对方作为自身属性。这种关系会造成潜在的强引用循环。

Customer 和 CreditCard 之间的关系和之前弱引用例子中的 Apartment 和 Person 之间的关系是截然不同的。在这个数据模型中,一个客户可能有也可能没有信用卡,但是,一张信用卡 总是 关联一个客户。 CreditCard 实例绝不能比它引用的 Customer 生命周期持久的。为了表示这一点, Customer 类有一个名为 card 的可选类型属性, CreditCard 类有一个名为 customer 的无主引用(非可选类型)属性。

此外,只能 通过给自定义构造函数传递一个  number  值和一个 customer  实例来创建 CreditCard 实例。这就保证了只要 CreditCard 被创建,这个实例总有一个关联的 customer 实例。

由于一张信用卡总要关联一个客户,所以将它的 customer 属性定义为无主引用,这样就能避免强引用循环:

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 属性在 23 位和 64 位系统都有足够的空间存储 16 位数字的卡号。

接下来的代码定义了一个名叫 john 的 Customer 可选类型的变量,这将用于存储一个特定的客户。由于是可选类型的,所以初始化为 nil :

var john: Customer?

现在你可以创建一个 Customer 实例,然后用它初始化 CreditCard 实例,并将新创建的 CreditCard 实例赋值给客户的 card 属性:

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

现在关联好这两个实例之后,引用关系图如下:

0

现在 Customer 实例持有一个指向 CreditCard 实例的强引用,而 CreditCard 实例持有一个指向 Customer 实例的无主引用。

因为 customer 是无主引用,所以当你断开变量 john 持有的强引用时,那就没有任何强引用指向 Customer 实例:

0

因为没有强引用指向 Customer 实例,所以它被释放。之后,CreditCard 实例也没有指向自己的强引用,也随之被释放了:

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

上面的最后一段代码展示了在将变量 john 设置为 nil 之后,Customer 实例和 CreditCard 实例的析构函数都打印了「 deinitialized 」信息。

注意

上面的例子展示了如何 安全 的使用无主引用。对于需要禁掉运行时安全检查的情况,Swift 也提供了不安全的无主引用--例如,出于性能考虑。想其他不安全操作一样,你需要负责检查代码的安全性。

unowned(unsafe) 表示这是一个不安全的无主引用。当你试图访问已经释放了的不安全的无主引用实例时,程序会试图访问该实例指向的内存区域,这是一种不安全的操作。

无主引用及隐式解析可选类型

上面的弱引用和无主引用的例子涵盖了两者常见的需要打破强引用循环的场景。

Person 和 Apartment 的例子展示了两个属性都允许设置为 nil,并会造成潜在的强引用循环。这种场景最适合用弱引用来解决。

Customer 和 CreditCard 的例子展示了一个属性允许设置为 nil,而另一个属性不允许设置为 nil,并会造成潜在的强引用循环。这种场景最适合用无主引用来解决。

然而,还有第三种场景,两个属性 必须有值,初始化之后属性都不能为 nil。在这场景下,需要一个类使用无主引用属性,另一个类使用隐式解析可选类型属性。

这使得初始化完成之后两个属性都能直接访问(不需要显式解包),同时避免了强引用循环。这一节将为你展示如何建立这种关系。

接下来的例子定义了两个类,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 属性。

Country 构造函数调用 City 的构造函数。然而,只有在 Country 实例初始化完成后,Country 的构造函数才能将 self 传递给 City 的构造函数,可参见「两段式构造」。

为实现这种需求,需要通过在类型注解后面加上感叹号( City! ),将 CountrycapitalCity 属性声明为隐式解析可选类型的属性。这也就意味着 capitalCity 属性像其他可选类型一样,默认值为 nil,但是不需要显示解包就能访问其值,可参见「隐式解析可选类型」。

由于 capitalCity 初始化值为 nil,一旦 Country 实例的构造函数设置 name 属性之后,整个初始化过程就完成了。这也就意味着,Country的构造函数一旦完成对 name 属性的设置,就可以引用和传递隐式的 self。因而, Country 构造函数在设置自己的 capitalCity 属性的时候可以将 self 作为参数传递给 City 的构造函数。

以上说明,你可以在一条语句中同时创建 Country 和 City 实例,而且不会产生强引用循环, 同时 capitalCity 属性可以直接访问,而不用通过感叹号(!)强制解包:

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

在上面的例子中,使用隐式解析可选类型意义在于能满足两段构造的需求。一旦初始化结束,capitalCity 属性可以像非可选类型一样直接使用和访问,同时还能避免产生强引用循环。


闭包引起的强引用循环

前面我们了解到两个实例互相强引用对方会产生强引用循环。同时,也学习到使用弱引用(weak)和无主引用(unowned)打破强引用循环。

强引用循环还可能发生在将一个闭包赋值给一个实例的属性,并且这个闭包又捕获到这个实例的时候。捕获的原因可能是在闭包的内部需要访问实例的属性,比如 self.someProperty 或者是在闭包内部调用实例的某个方法,比如 self.someMethod()。这两种情况都会导致闭包 「捕获」 self,从而产生强引用循环。

强引用循环产生的原因是闭包和类相似,都是 引用类型。当你把一个闭包赋值给一个属性时,其实赋值的是这个闭包的 引用。本质上,他和之前的问题一样--都是两个强引用互相持有对方不被释放。但是这一次是一个实例和一个闭包互相持有对方不被释放,而不是两个实例。

Swift 提供了一个优雅的解决方案,称之为 闭包捕获列表closure capture list)。然而,在学习如何利用闭包捕获列表打断强引用循环之前,了解这种情况下强引用循环如何产生的,对我们的帮助更大。

接下来的例子展示一个闭包引用了 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」,段落 「p」,或者换行 「br」。同时 HTMLElement 还定义了一个可选类型的 text 属性,你可以将其设置为要在 HTML 中呈现的字符串。

除了上面的两个简单的属性, HTMLElement 类还定义了一个 lazy 属性 asHTML。这个属性引用一个将 name 和 text 组成 HTML 字符串片段的闭包。asHTML 属性是 () -> String 类型的,或者可以理解为「一个没有参数,返回 String 类型的函数」。

默认情况下,asHTML 属性被赋值了一个闭包,这个闭包返回了一个代表 HTML 标签的字符串。如果 text 有值,这个标签就包含 text 的值,如果 text 没值,这个标签就不包含内容。对于一个段落元素,闭包返回值是 「<p>some text</p>」 还是 「<p />」,取决于 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())
// 打印 "<h1>some default text</h1>"

注意

 asHTML声明为 lazy 属性,是因为只有当元素实际呈现给外部输出的时候才需要它。事实上, asHTMLlazy 属性,意味着可以在默认的闭包内部引用  self,可以这样做的原因是 lazy 属性在初始化完成之前都不会被访问,并且此时 self 是已知存在的。

HTMLElement 类只提供了一个构造函数,这个构造函数通过 name 参数和 text(如果有的话)参数来初始化一个新的元素。这个类也定义了析构函数,在 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 的默认值的闭包之间产生强引用循环。强引用循环如下图:

0

实例的 asHTML 属性对它的闭包持有一个强引用。然而,闭包在其内部引用了 self (像这样引用 self.name 和 self.text ),因此闭包 捕获 self,这意味着闭包反过来对 HTMLElement 实例持有一个强引用。这样强引用循环就在他俩之间产生了。(更多关于闭包捕获的信息请参考「值捕获」。)

注意

闭包虽然多次引用了 self,但其实它只捕获了一个对 HTMLElement 实例的强引用。

如果你设置变量 paragraph 为 nil,断开它对 HTMLElement 实例的强引用,但是,HTMLElement 实例和它的闭包都不释放,这是因为强引用循环:

paragraph = nil

我们注意到 HTMLElement 实例的析构函数中的消息并没有打印,这证明了 HTMLElement 实例并没有释放。


解决闭包引起的强引用循环

定义闭包的时候同时定义 捕获列表 ,并作为闭包的一部分,通过这种方式可以解决闭包和实例之间的强引用循环。捕获列表定义了在闭包内部捕获一个或多个引用类型的规则。像解决两个实例的强引用循环一样,将每一个捕获类型声明为弱引用(weak)或是无主引用(unowned),而不是声明为强引用。至于是使用弱引用(weak)还是无主引用(unowned)取决于你代码中的不同部分之间的关系。

注意

Swift强制要求
闭包内部使用 self 的成员,必须要写成 self.someProperty 或 self.someMethod() (而不是仅仅写成 someProperty 或 someMethod())。这提醒你可能会一不小心就捕获了 self

定义捕获列表

捕获列表中的每一项都是由 weak 或 unowned 关键字和实例的引用(如 self)或是由其他值初始化的变量(如delegate = self.delegate!)成组构成的。它们每一组都写在方括号中,组之间用逗号隔开。 

捕获列表放在闭包的参数和返回值(如果有返回值的话)前面:

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

如果一个闭包没有指定的参数列表或是返回值,则可以在上下文中推断出来,此时要把捕获列表放在闭包的开始位置,其后跟着关键字 in :

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

弱引用和无主引用

当闭包和它捕获的实例始终互相持有对方的时候,将闭包的捕获定义为无主引用,那闭包和它捕获的实例总会同时释放。

相反的,将捕获定义弱引用时,捕获的引用也许会在将来的某一时刻变成 nil。弱引用总是可选类型的,并且,当引用的实例释放的时候,弱引用自动变成 nil。 这就需要你在闭包内部检查它的值是否存在。

注意

如果捕获的引用绝不会为 nil,那捕获方式应该总是无主引用而不是弱引用。

对于前面的 「强引用循环」 一章中的 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

和之前一样,创建并打印一个 HTMLElement 实例:

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

使用捕获列表之后,引用关系图如下:

0

这一次,闭包以无主引用的方式捕获 self ,并且不会对它捕获的 HTMLElement 实例持有强引用。如果将变量 paragraph 持有的强引用设置为 nil, HTMLElement 实例就会释放,紧接着就会看到它的析构函数打印如下消息:

paragraph = nil
// 打印 "p is being deinitialized"

更多关于捕获列表的信息,参见 「捕获列表」。

本文章首发在 LearnKu.com 网站上。

本译文仅用于学习和交流目的,转载请务必注明文章译者、出处、和本文链接
我们的翻译工作遵照 CC 协议,如果我们的工作有侵犯到您的权益,请及时联系我们。

原文地址:https://learnku.com/docs/the-swift-progr...

译文地址:https://learnku.com/docs/the-swift-progr...

上一篇 下一篇
讨论数量: 0
发起讨论 只看当前版本


暂无话题~