From 406249b7bb9ab3e9c5d64b1bc4ad78b619798b1c Mon Sep 17 00:00:00 2001 From: ChildhoodAndy Date: Fri, 6 Jun 2014 17:48:16 +0800 Subject: [PATCH 1/3] add 2-16 add 2-16 --- .../16_Automatic_Reference_Counting.md | 466 ++++++++++++++++++ 1 file changed, 466 insertions(+) diff --git a/source/chapter2/16_Automatic_Reference_Counting.md b/source/chapter2/16_Automatic_Reference_Counting.md index e69de29b..1aa6f460 100644 --- a/source/chapter2/16_Automatic_Reference_Counting.md +++ b/source/chapter2/16_Automatic_Reference_Counting.md @@ -0,0 +1,466 @@ +# 自动引用计数 +======================= + +Swift使用自动引用计数(ARC)来跟踪并管理应用使用的内存。大部分情况下,这意味着在Swift语言中,内存管理"仍然工作",不需要自己去考虑内存管理的事情。当实例不再被使用时,ARC会自动释放这些类的实例所占用的内存。 + +然而,在少数情况下,为了自动的管理内存空间,ARC需要了解关于你的代码片段之间关系的更多信息。本章描述了这些情况,并向大家展示如何打开ARC来管理应用的所有内存空间。 + +>注意
+引用计数只应用在类的实例。结构体(Structure)和枚举类型是值类型,并非引用类型,不是以引用的方式来存储和传递的。 + +## ARC如何工作 + +每次创建一个类的实例,ARC就会分配一个内存块,用来存储这个实例的相关信息。这个内存块保存着实例的类型,以及这个实例相关的属性的值。 + +当实例不再被使用时,ARC释放这个实例使用的内存,使这块内存可作它用。这保证了类实例不再被使用时,它们不会占用内存空间。 + +但是,如果ARC释放了仍在使用的实例,那么你就不能再访问这个实例的属性或者调用它的方法。如果你仍然试图访问这个实例,应用极有可能会崩溃。 + +为了保证不会发生上述的情况,ARC跟踪与类的实例相关的属性、常量以及变量的数量。只要有一个有效的引用,ARC都不会释放这个实例。 + +为了让这变成现实,只要你将一个类的实例赋值给一个属性或者常量或者变量,这个属性、常量或者变量就是这个实例的*强引用(strong reference)*。之所以称之为“强”引用,是因为它强持有这个实例,并且只要这个强引用还存在,就不允许销毁实例。 + +## ARC实践 + +下面的例子展示了ARC是如何工作的。本例定义了一个简单的类,类名是Person,并定义了一个名为name的常量属性: + + class Person { + let name: String + init(name: String) { + self.name = name + println("\(name) is being initialized") + } + + deinit { + println("\(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 "Jonh Appleseed is being initialized" + +注意,消息`“John Appleseed is being initialized”`在调用`Person`类的初始化函数时打印。这印证初始化确实发生了。 + +因为`Person`的实例赋值给了变量`reference1`,所以`reference1`是`Person`实例的强引用。又因为至少有这一个强引用,ARC就保证这个实例会保存在内存重而不会被销毁。 + +如果将这个`Person`实例赋值给另外的两个变量,那么将建立另外两个指向这个实例的强引用: + + reference2 = reference1 + reference3 = reference2 + +现在,这一个`Person`实例有三个强引用。 + +如果你通过赋值nil给两个变量来破坏其中的两个强引用(包括原始的引用),只剩下一个强引用,这个`Person`实例也不会被销毁: + + reference1 = nil + reference2 = nil + +直到第三个也是最后一个强引用被破坏,ARC才会销毁`Person`的实例,这时,有一点非常明确,你无法继续使用`Person`实例: + + referenece3 = nil + // 打印 “John Appleseed is being deinitialized” + + +## 类实例间的强引用环 + +在上面的例子中,ARC可以追踪`Person`实例的引用数量,并且在它不再被使用时销毁这个实例。 + +然而,我们有可能会写出这样的代码,一个类的实例永远不会有0个强引用。在两个类实例彼此保持对方的强引用,使得每个实例都使对方保持有效时会发生这种情况。我们称之为*强引用环*。 + +通过用弱引用或者无主引用来取代强引用,我们可以解决强引用环问题。在开始学习如何解决这个问题之前,理解它产生的原因会很有帮助。 + +下面的例子展示了一个强引用环是如何在不经意之间产生的。例子定义了两个类,分别叫`Person`和`Apartment`,这两个类建模了一座公寓以及它的居民: + + class Person { + let name: String + init(name: String) { self.name = name } + var apartment: Apartment? + deinit { println("\(name) is being deinitialized") } + } + + class Apartment { + let number: Int + init(number: Int) { self.number = number } + var tenant: Person? + deinit { println("Apartment #\(number) is being deinitialized") } + } + +每个`Person`实例拥有一个`String`类型的`name`属性以及一个被初始化为nil的`apartment`可选属性。`apartment`属性是可选的,因为一个人并不一定拥有一座公寓。 + +类似的,每个`Apartment`实例拥有一个`Int`类型的`number`属性以及一个初始化为nil的`tenant`可选属性。`tenant`属性是可选的,因为一个公寓并不一定有居民。 + +这两个类也都定义了初始化函数,打印消息表明这个类的实例正在被初始化。这使你能够看到`Person`和`Apartment`的实例是否像预期的那样被销毁了。 + +下面的代码片段定义了两个可选类型变量,`john`和`number73`,分别被赋值为特定的`Apartment`和`Person`的实例。得益于可选类型的优点,这两个变量初始值均为nil: + + var john: Person? + var number73: Apartment? + +现在,你可以创建特定的`Person`实例以及`Apartment`实例,并赋值给`john`和`number73`: + + jhon = Person(name: "John Appleseed") + number73 = Apartments(number: 73) + +下面的图表明了在创建以及赋值这两个实例后强引用的关系。`john`拥有一个`Person`实例的强引用,`number73`拥有一个`Apartment`实例的强引用: +![Resize icon](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Art/referenceCycle01_2x.png) + +现在你可以将两个实例关联起来,一个人拥有一所公寓,一个公寓也拥有一个房客。注意:用感叹号(!)来展开并访问可选类型的变量,只有这样这些变量才能被赋值: + + john!.apartment = number73 + number73!.tenant = john + +两个实例关联起来后,强引用关系如下图所示: +![Resize icon](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Art/referenceCycle02_2x.png) + +糟糕的是,关联这俩实例生成了一个强引用环,`Person`实例和`Apartment`实例各持有一个对方的强引用。因此,即使你破坏`john`和`number73`所持有的强引用,引用计数也不会变为0,因此ARC不会销毁这两个实例: + + john = nil + nuber73 = nil + +注意,当上面两个变量赋值为nil时,没有调用任何一个deinitializer。强引用环阻止了`Person`和`Apartment`实例的销毁,进一步导致内存泄漏。 + +此时强引用关系如下图所示: +![Resize icon](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Art/referenceCycle03_2x.png) + +`Person`和`Apartment`实例之间的强引用依然存在。 + +## 解决实例间的强引用环 +Swift提供两种方法来解决强引用环:弱引用和无主引用。 + +弱引用和无主引用允许引用环中的一个实例引用另外一个实例,但不是强引用。因此实例可以互相引用但是不会产生强引用环。 + +对于生命周期中引用会变为nil的实例,使用弱引用;对于初始化时赋值之后引用再也不会赋值为nil的实例,使用无主引用。 + +### 弱引用 + +`弱引用`不会增加实例的引用计数,因此不会阻止ARC销毁被引用的实例。这种特性使得引用不会变成强引用环。声明属性或者变量的时候,关键字`weak`表明引用为弱引用。 + +在实例的生命周期中,如果某些时候引用没有值,那么弱引用可以阻止强引用环。如果整个生命周期内引用`都有`值,那么相应的用无主引用,在`无主引用`这一章中有详细描述。在上面的`Apartment`例子中,有时一个`Apartment`实例可能没有房客,因此此处应该用弱引用。 +>注意
+>弱引用只能声明为变量类型,因为运行时它的值可能改变。弱引用绝对不能声明为常量。 + +因为弱引用可以没有值,所以声明弱引用的时候必须是可选类型的。在Swift语言中,推荐用可选类型来作为可能没有值的引用的类型。 + +如前所述,弱引用不会保持实例,因此即使实例的弱引用依然存在,ARC也有可能会销毁实例,并将弱引用赋值为`nil`。你可以想检查其他的可选值一样检查弱引用是否存在,永远也不会碰到引用了也被销毁的实例的情况。 + +下面的例子和之前的`Person`和`Apartment`例子相似,除了一个重要的区别。这一次,我们声明`Apartment`的`tenant`属性为弱引用: + + class Person { + let name: String + init(name: String) { self.name = name } + var apartment: Apartment? + deinit { println("\(name) is being deinitialized") } + } + + class Apartment { + let number: Int + init(number: Int) { self.number = number } + weak var tenant: Person? + deinit { println("Apartment #\(number) is being deinitialized") } + } + +然后创建两个变量(`john`和`number73`)的强引用,并关联这两个实例: + + var john: Person? + var number73: Apartment? + + john = Person(name: "John Appleseed") + number73 = Apartment(nunber: 73) + + john!.apartment = number73 + number73!.tenant = john + +下面是引用的关系图: +![Resize icon](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Art/weakReference01_2x.png) + +`Person`的实例仍然是`Apartment`实例的强引用,但是`Apartment`实例则是`Person`实例的弱引用。这意味着当破坏`john`变量所持有的强引用后,不再存在任何`Person`实例的强引用: +![Resize icon](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Art/weakReference02_2x.png) + +既然不存在`Person`实例的强引用,那么该实例就会被销毁: + + john = nil + // 打印"John Appleseed is being deinitialized" + +只有`number73`还持有`Apartment`实例的强引用。如果你破坏这个强引用,那么也不存在`Apartment`实例的任何强引用: +![Resize icon](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Art/weakReference03_2x.png) + +这时,`Apartment`实例也被销毁: + + number73 = nil + // 打印"Apartment #73 is being deinitialized" + +上面的两段代码表明在`john`和`number73`赋值为`nil`后,`Person`和`Apartment`实例的deinitializer都打印了“销毁”的消息。这证明了引用环已经被打破了。 + +### 无主引用 +和弱引用相似,无主引用也不强持有实例。但是和弱引用不同的是,无主引用默认始终有值。因此,无主引用只能定义为非可选类型(non-optional type)。在属性、变量前添加`unowned`关键字,可以声明一个无主引用。 + +因为是非可选类型,因此当使用无主引用的时候,不需要展开,可以直接访问。不过非可选类型变量不能赋值为`nil`,因此当实例被销毁的时候,ARC无法将引用赋值为`nil`。 +>注意
+当实例被销毁后,试图访问该实例的无主引用会触发运行时错误。使用无主引用时请确保引用始终指向一个未销毁的实例。 +上面的非法操作会百分百让应用崩溃,不会发生无法预期的行为。因此,你应该避免这种情况。 + +接下来的例子定义了两个类,`Customer`和`CreditCard`,模拟了银行客户和客户的信用卡。每个类都一个属性,存储另外一个类的实例。这样的关系可能会产生强引用环。 + +`Customer`、`CreditCard`的关系和之前弱引用例子中的`Apartment`、`Person`的关系截然不同。在这个模型中,消费者不一定有信用卡,但是每张信用卡一定对应一个消费者。鉴于这种关系,`Customer`类有一个可选类型属性`card`,而`CreditCard`类的`customer`属性则是非可选类型的。 + +进一步,要创建一个`CreditCard`实例,只能通过传递`number`值和`customer`实例到定制的`CreditCard`初始化函数来完成。这样可以确保当创建`CreditCard`实例时总是有一个`customer`实例与之关联。 + +因为信用卡总是对应一个消费者,因此定义`customer`属性为无主引用,这样可以避免强引用环: + + class Customer { + let name: String + var card: CreditCard? + init(name: String) { + self.name = name + } + + deinit { println("\(name) is being deinitialized") + } + + class CreditCard { + let number: Int + unowned let customer: Customer + init(number: Int, customer: Customer) { + self.number = number + self.customer = customer + } + + deinit { println("Card #\(number) is being deinitialized") + } + +下面的代码定义了一个叫`john`的可选类型`Customer`变量,用来保存某个特定消费者的引用。因为是可变类型,该变量的初始值为`nil`: + + var john: Customer? + +现在创建一个`Customer`实例,然后用它来初始化`CreditCard`实例,并把刚创建出来的`CreditCard`实例赋值给`Customer`的`card`属性: + + john = Customer(name: "John Appleseed") + john!.card = CreditCard(number: 1234_5678_9012_3456, customer:john!) + +我们来看看此时的引用关系: +![Resize icon](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Art/unownedReference01_2x.png) + +`Customer`实例持有`CreditCard`实例的强引用,而`CreditCard`实例则持有`Customer`实例的无主引用。 + +因为`customer`的无主引用,当破坏`john`变量持有的强引用时,就没有`Customer`实例的强引用了: +![Resize icon](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Art/unownedReference02_2x.png) + +此时`Customer`实例被销毁。然后,`CreditCard`实例的强引用也不复存在,因此`CreditCard`实例也被销毁: + + john = nil + // 打印"John Appleseed is being deinitialized" + // 打印"Card #1234567890123456 is being deinitialized" + +上面的代码证明,`john`变量赋值为`nil`后,`Customer`实例和`CreditCard`实例的deinitializer方法都打印了"deinitialized"消息。 + +### 无主引用以及显式展开的可选属性 +上述的弱引用和无主引用的例子覆盖了两种常用的需要打破强引用环的应用场景。 + +`Person`和`Apartment`的例子说明了下面的场景:两个属性的值都可能是`nil`,并有可能产生强引用环。这种场景下适合使用弱引用。 + +`Customer`和`CreditCard`的例子则说明了另外的场景:一个属性可以是`nil`,另外一个属性不允许是`nil`,并有可能产生强引用环。这种场景下适合使用无主引用。 + +但是,存在第三种场景:两个属性都必须有值,且初始化完成后不能为`nil`。这种场景下,则要一个类用无主引用属性,另一个类用显式展开的可选属性。 + +这样,在初始化完成后我们可以立即访问这两个变量(而不需要可选展开),同时又避免了引用环。本节将告诉你应该如何配置这样的关系。 + +下面的例子顶一个了两个类,`Country`和`City`,都有一个属性用来保存另外的类的实例。在这个模型里,每个国家都有首都,每个城市都隶属于一个国家。所以,类`Country`有一个`capitalCity`属性,类`City`有一个`country`属性: + + class Country { + let name: String + let 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`的实例完全初始化完后(在Two-Phase Initialization),`Country`的初始化函数才能把`self`传给`City`的初始化函数。 + +为满足这种需求,通过在类型结尾处加感叹号(City!),我们声明`Country`的`capitalCity`属性为显式展开的可选类型属性。就是说,`capitalCity`属性的默认值是`nil`,不需要展开它的值(在Implicity Unwrapped Optionals中描述)就可以直接访问。 + +因为`capitalCity`默认值是`nil`,一旦`Country`的实例在初始化时给`name`属性赋值后,整个初始化过程就完成了。这代表只要赋值`name`属性后,`Country`的初始化函数就能引用并传递显式的`self`。所以,当`Country`的初始化函数在赋值`capitalCity`时,它也可以将`self`作为参数传递给`City`的初始化函数。 + +综上所述,你可以在一条语句中同时创建`Country`和`City`的实例,却不会产生强引用环,并且不需要使用感叹号来展开它的可选值就可以直接访问`capitalCity`: + + var country = Country(name: "Canada", capitalName: "Ottawa") + println("\(country.name)'s captial city is called \(country.capitalCity.name)") + // 打印"Canada's capital city is called Ottawa" + +在上面的例子中,使用显式展开的可选值满足了两个类的初始化函数的要求。初始化完成后,`capitalCity`属性就可以做为非可选值类型使用,却不会产生强引用环。 + +## 闭包产生的强引用环 + +前面我们看到了强引用环是如何产生的,还知道了如何引入弱引用和无主引用来打破引用环。 + +将一个闭包赋值给类实例的某个属性,并且这个闭包使用了实例,这样也会产生强引用环。这个闭包可能访问了实例的某个属性,例如`self.someProperty`,或者调用了实例的某个方法,例如`self.someMethod`。这两种情况都导致了闭包使用`self`,从而产生了抢引用环。 + +因为诸如类这样的闭包是*引用*类型,导致了强引用环。当你把一个闭包赋值给某个属性时,你也把一个*引用*赋值给了这个闭包。实质上,这个之前描述的问题是一样的-两个强引用让彼此一直有效。但是,和两个类实例不同,这次一个是类实例,另一个是闭包。 + +Swift提供了一种优雅的方法来解决这个问题,我们称之为*闭包占用列表(closuer 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)" + } else { + return "<\(self.name) />" + } + } + + init(name: String, text: String? = nil) { + self.name = name + self.text = text + } + + deinit { + println("\(name) is being deinitialized") + } + + } + +类`HTMLElement`定义了一个`name`属性来表示这个元素的名称,例如代表段落的`"p"`,或者代表换行的`"br"`;以及一个可选属性`text`,用来设置HTML元素的文本。 + +除了上面的两个属性,`HTMLElement`还定义了一个lazy属性`asHTML`。这个属性引用了一个闭包,将`name`和`text`组合成HTML字符串片段。该属性是`() -> String`类型,就是“没有参数,返回`String`的函数”。 + +默认将闭包赋值给了`asHTML`属性,这个闭包返回一个代表HTML标签的字符串。如果`text`值存在,该标签就包含可选值`text`;或者不包含文本。对于段落,根据`text`是`"some text"`还是`nil`,闭包会返回`"

some text

"`或者`"

"`。 + +可以像实例方法那样去命名、使用`asHTML`。然而,因为`asHTML`终究是闭包而不是实例方法,如果你像改变特定元素的HTML处理的话,可以用定制的闭包来取代默认值。 +>注意
+`asHTML`声明为lazy属性,因为只有当元素确实需要处理为HTML输出的字符串时,才需要使用`asHTML`。也就是说,在默认的闭包中可以使用`self`,因为只有当初始化完成以及`self`确实存在后,才能访问lazy属性。 + +`HTMLElement`只有一个初始化函数,根据`name`和`text`(如果有的话)参数来初始化一个元素。该类也定义了一个deinitializer,当`HTMLElement`实例被销毁时,打印一条消息。 + +下面的代码创建一个`HTMLElement`实例并打印消息。 + + var paragraph: HTMLElement? = HTMLElement(name: "p", text: "hello, world") + println(paragraph!.asHTML()) + // 打印"

hello, world

" + +>注意
+上面的`paragraph`变量定义为可选`HTMLElement`,因此我们可以赋值`nil`给它来演示强引用环。 + +不幸的是,`HTMLElement`类产生了类实例和`asHTML`默认值的闭包之间的强引用环。如下图所示: + +![Resize icon](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Art/closureReferenceCycle01_2x.png) + +实例的`asHTML`属性持有闭包的强引用。 + +但是,闭包使用了`self`(引用了`self.name`和`self.text`),因此闭包*占有*了`self`,这意味着闭包又反过来持有了`HTMLElement`实例的强引用。这样就产生了强引用环。(更多闭包哪占有值的信息,请参考Capturing Values)。 + +>注意
+虽然闭包多次使用了`self`,它只占有`HTMLElement`实例的一个强引用。 + +如果设置`paragraph`为`nil`,打破它持有的`HTMLElement`实例的强引用,`HTMLElement`实例和它的闭包都不会被销毁,就因为强引用环: + + paragraph = nil + +注意`HTMLElement`deinitializer中的消息并没有别打印,印证了`HTMLElement`实例并没有被销毁。 + + +## 解决闭包产生的强引用环 + +在定义闭包时同时定义*占有列表*作为闭包的一部分,可以解决闭包和类实例之间的强引用环。占有列表定义了闭包内占有一个或者多个引用类型的规则。和解决两个类实例间的强引用环一样,声明每个占有的引用为弱引用或无主引用,而不是强引用。根据代码关系来决定使用弱引用还是无主引用。 + +>注意
+Swift有如下约束:只要在闭包内使用`self`的成员,就要用`self.someProperty`或者`self.someMethod`(而非只是`someProperty`或`someMethod`)。这可以提醒你可能会不小心就占有了`self`。 + +### 定义占有列表 + +占有列表中的每个元素都是由`weak`或者`unowned`关键字和实例的引用(如`self`或`someInstance`)组成。每一对都在花括号中,通过逗号分开。 + +占有列表放置在闭包参数列表和返回类型之前: + + @lazy var someClosure: (Int, String) -> String = { + [unowned self] (index: Int, stringToProcess: String) -> String in + // closure body goes here + } + +如果闭包没有指定参数列表或者返回类型(可以通过上下文推断),那么占有列表放在闭包开始的地方,跟着是关键字`in`: + + @lazy var someClosure: () -> String = { + [unowned self] 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)" + } else { + return "<\(self.name) />" + } + } + + init(name: String, text: String? = nil) { + self.name = name + self.text = text + } + + deinit { + println("\(name) is being deinitialized") + } + + } + +上面的`HTMLElement`实现和之前的实现相同,只是多了占有列表。这里,占有列表是`[unowned self]`,代表“用无主引用而不是强引用来占有`self`”。 + +和之前一样,我们可以创建并打印`HTMLElement`实例: + + var paragraph: HTMLElement? = HTMLElement(name: "p", text: "hello, world") + println(paragraph!.asTHML()) + // 打印"

hello, world

" + +使用占有列表后引用关系如下图所示: + +![Resize icon](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Art/closureReferenceCycle02_2x.png) + +这一次,闭包以无主引用的形式占有`self`,并不会持有`HTMLElement`实例的强引用。如果赋值`paragraph`为`nil`,`HTMLElement`实例将会被销毁,并能看到它的deinitializer打印的消息。 + + paragraph = nil + // 打印"p is being deinitialized" From 15fd25ac37be44d0ba757ac4b6359fdd14a22e9e Mon Sep 17 00:00:00 2001 From: ChildhoodAndy Date: Fri, 6 Jun 2014 17:48:59 +0800 Subject: [PATCH 2/3] add 2-22 add 2-22 --- source/chapter2/22_Generics.md | 475 +++++++++++++++++++++++++++++++++ 1 file changed, 475 insertions(+) diff --git a/source/chapter2/22_Generics.md b/source/chapter2/22_Generics.md index e69de29b..787fc460 100644 --- a/source/chapter2/22_Generics.md +++ b/source/chapter2/22_Generics.md @@ -0,0 +1,475 @@ +# 泛型 + +------ + +*泛型代码*可以确保你写出灵活的,可重用的函数和定义出任何你所确定好的需求的类型。你的可以写出避免重复的代码,并且用一种清晰的,抽象的方式表达出来。 + +泛型是Swift需要强大特征中的其中一个,许多Swift标准库是通过泛型代码构建出来的。事实上,你已经使用泛型贯穿着整个Language Guide,即便你没有实现它。例如:Swift的Array和Dictionary类型都是泛型集。你可以创建一个Int数组,也可创建一个String数组,或者甚至于可以是任何其他Swift的类型数据数组。同样的,你也可以创建存储任何指定类型的字典(dictionary),而且这些类型可以是没有限制的。 + +------ + +## 泛型所解决的问题 + +这里是一个标准的,非泛型函数swapTwoInts,用来交换两个Int值: + +```C + func swapTwoInts(inout a: Int, inout b: Int) + let temporaryA = a + a = b + b = temporaryA + } +``` +这个函数使用in-out参数交换a和b的值,这两个参数被描述为[In-Out类型参数][1]。 + +`swapTwoInts`函数可以交换b的原始值到a,也可以交换a的原始值到b,你可以调用这个函数交换两个Int变量值: +```C + var someInt = 3 + var anotherInt = 107 + swapTwoInts(&someInt, &anotherInt) + println("someInt is now \(someInt), and anotherInt is now \(anotherInt)") + // prints "someInt is now 107, and anotherInt is now 3" +``` + +`swapTwoInts`函数是非常有用的,但是它只能交换Int值,如果你想要交换两个String或者Double,就不得不写更多的函数,如 `swapTwoStrings`和`swapTwoDoublesfunctions `,如同如下所示: + +```C + func swapTwoStrings(inout a: String, inout b: String) { + let temporaryA = a + a = b + b = temporaryA + } + + func swapTwoDoubles(inout a: Double, inout b: Double) { + let temporaryA = a + a = b + b = temporaryA + } +``` + +你可能注意到 `swapTwoInts`、 `swapTwoStrings`和`swapTwoDoubles`函数主题都是相同的,唯一不同之处就在于传入的变量不同,分别是Int、String和Double。 + +但实际应用中通常需要一个用处更强大并且尽可能的考虑到更多的灵活性单个函数,可以用来交换两个任何类型值,很幸运的是,泛型代码帮你解决了这种问题。(一个这种泛型函数后面已经定义好了。) + +>NOTE + +>In all three functions, it is important that the types of a and b are defined to be the same as each other. If a and b were not of the same type, it would not be possible to swap their values. Swift is a type-safe language, and does not allow (for example) a variable of type String and a variable of type Double to swap values with each other. Attempting to do so would be reported as a compile-time error. + + +## 泛型函数 + +`泛型函数`可以工作于任何类型,这里是一个上面`swapTwoInts`函数的泛型版本,用于交换两个值: +```C + func swapTwoValues(inout a: T, inout b: T) { + let temporaryA = a + a = b + b = temporaryA + } +``` +`swapTwoValues`函数主体和`swapTwoInts`函数是一样,而且,只在第一行稍微有那么一点点不同于`swapTwoInts`,如下所示: +```C + func swapTwoInts(inout a: Int, inout b: Int) + func swapTwoValues(inout a: T, inout b: T) +``` + +这个函数的泛型版本使用了节点类型命名(通常此情况下用字母T来表示)来代替实际类型名(如Int、String或Double)。节点类型名并不是表示T必须是任何类型,但是其规定a和b必须是同一类型的T,而不管T表示任何类型。只有`swapTwoValues`函数在每次调用时所传入的实际类型决定了T所代表的类型。 + +另外一个不同之处在于这个泛型函数名后面跟着的节点类型名(T)是用尖括号括起来的()。这个尖括号告诉Swift那个T是`swapTwoValues`函数所定义的一个节点类型。因为T是一个节点,Swift不会去查找每一个命名为T的实际类型。 + +`swapTwoValues`函数除了只要传入的两个任何类型值是同一类型外,也可以作为`swapTwoInts`函数被调用。每次`swapTwoValues`被调用,T所代表的类型值都会传给函数。 + +在下面的两个例子中,T分别代表Tnt和String: + +```C + var someInt = 3 + var anotherInt = 107 + swapTwoValues(&someInt, &anotherInt) + // someInt is now 107, and anotherInt is now 3 + + var someString = "hello" + var anotherString = "world" + swapTwoValues(&someString, &anotherString) + // someString is now "world", and anotherString is now "hello" + +``` + +>NOTE + +>The swapTwoValues function defined above is inspired by a generic function called swap, which is part of the Swift standard library, and is automatically made available for you to use in your apps. If you need the behavior of the swapTwoValues function in your own code, you can use Swift’s existing swap function rather than providing your own implementation. + +##Type Parameters +## 类型参数 + +在上面的`swapTwoValues`例子中,节点类型T是一种类型参数的示例。类型参数指定并命名为一个节点类型,并且紧随在函数名后面,并用一对尖括号括起来(如)。 + +一旦一个类型参数被指定,那么其可以被使用来定义一个函数的参数类型(如`swapTwoValues`函数中的参数a和b),或作为一个函数返回类型,或用作函数主体中的注释类型。在这种情况下,被类型参数所代表的节点类型不管函数任何时候被调用,都会被实际类型所替换(在上面`swapTwoValues`例子中,当函数第一次被调用时,T被Int替换,第二次调用时,被String替换。)。 + +你可支持多个类型参数,命名在尖括号中,用逗号分开。 + +## 命名类型参数 + +在简单的情况下,泛型函数或泛型类型需要指定一个节点类型(如上面的`swapTwoValues`泛型函数,或一个存储单一类型的泛型集,如Array),通常用一单个字母T来命名类型参数。不过,你可以使用任何有效的标识符来作为类型参数名。 + +如果你使用多个参数定义更复杂的泛型函数或泛型类型,那么使用更多的描述类型参数是非常有用的。例如,Swift字典(Dictionary)类型有两个类型参数,一个是key,另外一个是值。如果你自己写字典,你或许会定义这两个类型参数为KeyType和ValueType,用来记住它们在你的泛型代码中的作用。 + +>NOTE + +>Always give type parameters UpperCamelCase names (such as T and KeyType) to indicate that they are a placeholder for a type, not a value. + +## 泛型类型 + + +通常在泛型函数中,Swift允许你定义你自己的泛型类型。这些自定义类、结构体和枚举作用于任何类型,如同`Array`和`Dictionary`的用法。 + +这部分向你展示如何写一个泛型集类型-`Stack`(栈)。一个栈是一系列值域的集合,和array(数组)相似,但其是一个比Swift的`Array`类型更多限制的集合。一个数组可以允许其里面任何位置的插入/删除操作,而栈,只允许,只允许在集合的末端添加新的项(如同*push*一个新值进栈)。同样的一个栈也只能从末端移除项(如同*pop*一个值出栈)。 + +>NOTE + +>The concept of a stack is used by the UINavigationController class to model the view controllers in its navigation hierarchy. You call the UINavigationController class pushViewController:animated: method to add (or push) a view controller on to the navigation stack, and its popViewControllerAnimated: method to remove (or pop) a view controller from the navigation stack. A stack is a useful collection model whenever you need a strict “last in, first out” approach to managing a collection. + +下图展示了一个栈的压栈(push)/出栈(pop)的行为: + +![此处输入图片的描述][2] + +1. 现在有三个值在栈中; +2. 第四个值“pushed”到栈的顶部; +3. 现在有四个值在栈中,最近的那个在顶部; +4. 栈中最顶部的那个项被移除,或称之为“popped”; +5. 移除掉一个值后,现在栈又重新只有三个值。 + +这里展示了如何写一个非泛型版本的栈,`Int`值型的栈: +```C +struct IntStack { + var items = Int[]() + mutating func push(item: Int) { + items.append(item) + } + mutating func pop() -> Int { + return items.removeLast() + } +} +``` + +这个结构体在栈中使用一个`Array`性质的`items`存储值。`Stack`提供两个方法:`push`和`pop`,从栈中压进一个值和移除一个值。这些方法标记为可变的,因为他们需要修改(或*转换*)结构体的`items`数组。 + +上面所展现的`IntStack`类型只能用于`Int`值,不过,其对于定义一个泛型`Stack`类(可以处理*任何*类型值的栈)是非常有用的。 + +这里是一个相同代码的泛型版本: + +```C +struct Stack { + var items = T[]() + mutating func push(item: T) { + items.append(item) + } + mutating func pop() -> T { + return items.removeLast() + } +} +``` + +注意到`Stack`的泛型版本基本上和非泛型版本相同,但是泛型版本的节点类型参数为T代替了实际`Int`类型。这种类型参数包含在一对尖括号里(``),紧随在结构体名字后面。 + +T定义了一个名为“某种类型T”的节点提供给后来用。这种将来类型可以在结构体的定义里任何地方表示为“T”。在这种情况下,T在如下三个地方被用作节点: + +- 创建一个名为`items`的属性,使用空的T类型值数组对其进行初始化; +- 指定一个包含一个参数名为`item`的`push`方法,该参数必须是T类型; +- 指定一个`pop`方法的返回值,该返回值将是一个T类型值。 + +当创建一个新单例并初始化时, 通过用一对紧随在类型名后的尖括号里写出实际指定栈用到类型,创建一个`Stack`实例,同创建`Array`和`Dictionary`一样: +```c +var stackOfStrings = Stack() +stackOfStrings.push("uno") +stackOfStrings.push("dos") +stackOfStrings.push("tres") +stackOfStrings.push("cuatro") +// 现在栈已经有4个string了 +``` +下图将展示`stackOfStrings`如何push这四个值进栈的过程: + +![此处输入图片的描述][3] + +从栈中pop并移除值"cuatro": + +```C +let fromTheTop = stackOfStrings.pop() +// fromTheTop is equal to "cuatro", and the stack now contains 3 strings +``` +下图展示了如何从栈中pop一个值的过程: +![此处输入图片的描述][4] + +由于`Stack`是泛型类型,所以在Swift中其可以用来创建任何有效类型的栈,这种方式如同`Array`和`Dictionary`。 + +##类型约束 + +`swapTwoValues`函数和`Stack`类型可以作用于任何类型,不过,有的时候对使用在泛型函数和泛型类型上的类型强制约束为某种特定类型是非常有用的。类型约束指定了一个必须继承自指定类的类型参数,或者遵循一个特定的协议或协议构成。 + +例如,Swift的`Dictionary`类型对作用于其keys的类型做了些限制。在[Dictionaries][5]的描述中,字典的keys类型必须是*hashable*,也就是说,必须有一种方法可以使其是唯一的表示。`Dictionary`之所以需要其keys是hashable是为了以便于其检查其是否包含某个特定key的值。如无此需求,`Dictionary`即不会告诉是否插入或者替换了某个特定key的值,也不能查找到已经存储在字典里面的给定key值。 + +这个需求强制加上一个类型约束作用于`Dictionary`的key上,当然其key类型必须遵循`Hashable`协议(Swift标准库中定义的一个特定协议)。所有的Swift基本类型(如`String`,`Int`, `Double`和 `Bool`)默认都是hashable。 + +当你创建自定义泛型类型时,你可以定义你自己的类型约束,当然,这些约束要支持泛型编程的强力特征中的多数。抽象概念如`Hashtable`具有的类型特征是根据他们概念特征来界定的,而不是他们的直接类型特征。 + +### 类型约束语法 + +你可以写一个在一个类型参数名后面的类型约束,通过冒号分割,来作为类型参数链的一部分。这种作用于泛型函数的类型约束的基础语法如下所示(和泛型类型的语法相同): +```c +func someFunction(someT: T, someU: U) { + // function body goes here +} +``` + +上面这个假定函数有两个类型参数。第一个类型参数`T`,有一个需要`T`必须是`SomeClass`子类的类型约束;第二个类型参数`U`,有一个需要`U`必须遵循`SomeProtocol`协议的类型约束。 + +### 类型约束行为 + +这里有个名为`findStringIndex`的非泛型函数,该函数功能是去查找包含一给定`String`值的数组。若查找到匹配的字符串,`findStringIndex`函数返回该字符串在数组中的索引值(`Int`),反之则返回`nil`: +```c +func findStringIndex(array: String[], valueToFind: String) -> Int? { + for (index, value) in enumerate(array) { + if value == valueToFind { + return index + } + } + return nil +} +``` + +`findStringIndex`函数可以作用于查找一字符串数组中的某个字符串: +```c +let strings = ["cat", "dog", "llama", "parakeet", "terrapin"] +if let foundIndex = findStringIndex(strings, "llama") { + println("The index of llama is \(foundIndex)") +} +// prints "The index of llama is 2" +``` + +如果只是针对字符串而言查找在数组中的某个值的索引,用处不是很大,不过,你可以写出相同功能的泛型函数`findIndex`,用某个类型`T`值替换掉提到的字符串。 + +这里展示如何写一个你或许期望的`findStringIndex`的泛型版本`findIndex`。请注意这个函数仍然返回`Int`,是不是有点迷惑呢,而不是泛型类型?那是因为函数返回的是一个可选的索引数,而不是从数组中得到的一个可选值。需要提醒的是,这个函数不会编译,原因在例子后面会说明: + +```c +func findIndex(array: T[], valueToFind: T) -> Int? { + for (index, value) in enumerate(array) { + if value == valueToFind { + return index + } + } + return nil +} +``` + +上面所写的函数不会编译。这个问题的位置在等式的检查上,`“if value == valueToFind”`。不是所有的Swift中的类型都可以用等式符(==)进行比较。例如,如果你创建一个你自己的类或结构体来表示一个复杂的数据模型,那么Swift没法猜到对于这个类或结构体而言“等于”的意思。正因如此,这部分代码不能可能保证工作于每个可能的类型`T`,当你试图编译这部分代码时估计会出现相应的错误。 + +不过,所有的这些并不会让我们无从下手。Swift标准库中定义了一个`Equatable`协议,该协议要求任何遵循的类型实现等式符(==)和不等符(!=)对任何两个该类型进行比较。所有的Swift标准类型自动支持`Equatable`协议。 + +任何`Equatable`类型都可以安全的使用在`findIndex`函数中,因为其保证支持等式操作。为了说明这个事实,当你定义一个函数时,你可以写一个`Equatable`类型约束作为类型参数定义的一部分: +```c +func findIndex(array: T[], valueToFind: T) -> Int? { + for (index, value) in enumerate(array) { + if value == valueToFind { + return index + } + } + return nil +} +``` + +`findIndex`中这个单个类型参数写做:`T: Equatable`,也就意味着“任何T类型都遵循`Equatable`协议”。 + +findIndex函数现在则可以成功的编译过,并且作用于任何遵循Equatable的类型,如Double或String: +```c +let doubleIndex = findIndex([3.14159, 0.1, 0.25], 9.3) +// doubleIndex is an optional Int with no value, because 9.3 is not in the array +let stringIndex = findIndex(["Mike", "Malcolm", "Andrea"], "Andrea") +// stringIndex is an optional Int containing a value of 2 +``` + +##关联类型 + +当定义一个协议时,有的时候声明一个或多个关联类型作为协议定义的一部分是非常有用的。一个关联类型给定作用于协议部分的类型一个节点名(或*别名*)。作用于关联类型上实际类型是不需要指定的,直到该协议接受。关联类型被指定为`typealias`关键字。 + +### 关联类型行为 + +这里是一个`Container`协议的例子,定义了一个ItemType关联类型: +```c +protocol Container { + typealias ItemType + mutating func append(item: ItemType) + var count: Int { get } + subscript(i: Int) -> ItemType { get } +} +``` +`Container`协议定义了三个任何容器必须支持的兼容要求: + +- 必须可能通过`append`方法添加一个新item到容器里; +- 必须可能通过使用`count`属性获取容器里items的数量,并返回一个`Int`值; +- 必须可能通过容器的`Int`索引值下标可以检索到每一个item。 + +这个协议没有指定容器里item是如何存储的或何种类型是允许的。这个协议只指定三个任何遵循`Container`类型所必须支持的功能点。一个遵循的类型也可以提供其他额外的功能,只要满足这三个条件。 + +任何遵循`Container`协议的类型必须指定存储在其里面的值类型,必须保证只有正确类型的items可以加进容器里,必须明确可以通过其下标返回item类型。 + +为了定义这三个条件,`Container`协议需要一个方法指定容器里的元素将会保留,而不需要知道特定容器的类型。`Container`协议需要指定任何通过`append`方法添加到容器里的值和容器里元素是相同类型,并且通过容器下标返回的容器元素类型的值的类型是相同类型。 + +为了达到此目的,`Container`协议声明了一个ItemType的关联类型,写作`typealias ItemType`。The protocol does not define what ItemType is an alias for—that information is left for any conforming type to provide(这个协议不会定义`ItemType`是遵循类型所提供的何种信息的别名)。尽管如此,`ItemType`别名支持一种方法识别在一个容器里的items类型,以及定义一种使用在`append`方法和下标中的类型,以便保证任何期望的`Container`的行为是强制性的。 + +这里是一个早前IntStack类型的非泛型版本,适用于遵循Container协议: +```c +struct IntStack: Container { + // original IntStack implementation + var items = Int[]() + mutating func push(item: Int) { + items.append(item) + } + mutating func pop() -> Int { + return items.removeLast() + } + // conformance to the Container protocol + typealias ItemType = Int + mutating func append(item: Int) { + self.push(item) + } + var count: Int { + return items.count + } + subscript(i: Int) -> Int { + return items[i] + } +} +``` + +`IntStack`类型实现了`Container`协议的所有三个要求,在`IntStack`类型的每个包含部分的功能都满足这些要求。 + +此外,`IntStack`指定了`Container`的实现,适用的ItemType被用作`Int`类型。对于这个`Container`协议实现而言,定义 `typealias ItemType = Int`,将抽象的`ItemType`类型转换为具体的`Int`类型。 + +感谢Swift类型参考,你不用在`IntStack`定义部分声明一个具体的`Int`的`ItemType`。由于`IntStack`遵循`Container`协议的所有要求,只要通过简单的查找`append`方法的item参数类型和下标返回的类型,Swift就可以推断出合适的`ItemType`来使用。确实,如果上面的代码中你删除了 `typealias ItemType = Int`这一行,一切仍旧可以工作,因为它清楚的知道ItemType使用的是何种类型。 + +你也可以生成遵循`Container`协议的泛型`Stack`类型: +```c +struct Stack: Container { + // original Stack implementation + var items = T[]() + mutating func push(item: T) { + items.append(item) + } + mutating func pop() -> T { + return items.removeLast() + } + // conformance to the Container protocol + mutating func append(item: T) { + self.push(item) + } + var count: Int { + return items.count + } + subscript(i: Int) -> T { + return items[i] + } +} +``` +这个时候,节点类型参数`T`被用作`append`方法的item参数和下标的返回类型。Swift因此可以推断出被用作这个特定容器的`ItemType`的`T`的合适类型。 + + +### 扩展一个存在的类型为一指定关联类型 + +在[Adding Protocol Conformance with an Extension][6]中有描述扩展一个存在的类型添加遵循一个协议。这个类型包含一个关联类型的协议。 + +Swift的`Array`已经提供`append`方法,一个`count`属性和通过下标来查找一个自己的元素。这三个功能都达到`Container`协议的要求。也就意味着你可以扩展`Array`去遵循`Container`协议,只要通过简单声明`Array`适用于该协议而已。如何实践这样一个空扩展,在[Declaring Protocol Adoption with an Extension][7]中有描述这样一个实现一个空扩展的行为: +```c +extension Array: Container {} +``` +如同上面的泛型`Stack`类型一样,`Array的append`方法和下标保证`Swift`可以推断出`ItemType`所使用的适用的类型。定义了这个扩展后,你可以将任何`Array`当作`Container`来使用。 + +## Where 语句 + +[Type Constraints][8]中描述的类型约束确保你定义关于类型参数的需求和一泛型函数或类型有关联。 + +对于关联类型的定义需求也是非常有用的。你可以通过这样去定义*where语句*作为一个类型参数队列的一部分。一个where语句使你能够要求一个关联类型遵循一个特定的协议,以及(或)那个特定的类型参数和关联类型可以是相同的。你可写一个where语句,通过紧随放置`where`关键字在类型参数队列后面,其后跟着一个或者多个针对关联类型的约束,以及(或)一个或多个类型和关联类型的等于关系。 + +下面的列子定义了一个名为`allItemsMatch`的泛型函数,用来检查是否两个`Container`单例包含具有相同顺序的相同items。如果匹配到所有的items,那么返回一个为true的Boolean值,反之,则相反。 + +这两个容器可以被检查出是否是相同类型的容器(虽然它们可以是),但他们确实拥有相同类型的items。这个需求通过一个类型约束和where语句结合来表示: +```c +func allItemsMatch< + C1: Container, C2: Container + where C1.ItemType == C2.ItemType, C1.ItemType: Equatable> + (someContainer: C1, anotherContainer: C2) -> Bool { + + // check that both containers contain the same number of items + if someContainer.count != anotherContainer.count { + return false + } + + // check each pair of items to see if they are equivalent + for i in 0..someContainer.count { + if someContainer[i] != anotherContainer[i] { + return false + } + } + + // all items match, so return true + return true + +} +``` + +这个函数用了两个参数:`someContainer`和`anotherContainer`。`someContainer`参数是类型`C1`,`anotherContainer`参数是类型`C2`。`C1`和`C2`是容器的两个节点类型参数,决定了这个函数何时被调用。 + +这个函数的类型参数列紧随在两个类型参数需求的后面: + +- `C1`必须遵循`Container`协议 (写作 `C1: Container`)。 +- `C2`必须遵循`Container`协议 (写作 `C2: Container`)。 +- `C1`的`ItemType`同样是C2的`ItemType`(写作 `C1.ItemType == C2.ItemType`)。 +- `C1`的`ItemType`必须遵循`Equatable`协议 (写作 `C1.ItemType: Equatable`)。 + +第三个和第四个要求被定义为一个where语句的一部分,写在关键字`where`后面,作为函数类型参数链的一部分。 + +这些要求意思是: + +`someContainer`是一个`C1`类型的容器。 +`anotherContainer`是一个`C2`类型的容器。 +`someContainer`和`anotherContainer`包含相同的items类型。 +`someContainer`中的items可以通过不等于操作(`!=`)来检查它们是否彼此不同。 + +第三个和第四个要求结合起来的意思是`anotherContainer`中的items也可以通过 `!=` 操作来检查,因为他们在`someContainer`中items确实是相同的类型。 + +这些要求能够使`allItemsMatch`函数比较两个容器,即便他们是不同的容器类型。 + +`allItemsMatch`首先检查两个容器是否拥有同样数目的items,如果他们的items数目不同,没有办法进行匹配,函数就会`false`。 + +检查完之后,函数通过`for-in`循环和半闭区间操作(..)来迭代`someContainer`中的所有items。对于每个item,函数检查是否`someContainer`中的item不等于对应的`anotherContainer`中的item,如果这两个items不等,则这两个容器不匹配,返回`false`。 + + +如果循环体结束后未发现没有任何的不匹配,那表明两个容器匹配,函数返回true。 + +Here’s how the allItemsMatch function looks in action: +这里演示了allItemsMatch函数运算的过程: +```c +var stackOfStrings = Stack() +stackOfStrings.push("uno") +stackOfStrings.push("dos") +stackOfStrings.push("tres") + +var arrayOfStrings = ["uno", "dos", "tres"] + +if allItemsMatch(stackOfStrings, arrayOfStrings) { + println("All items match.") +} else { + println("Not all items match.") +} +// prints "All items match." +``` + 上面的例子创建一个`Stack`单例来存储`String`,然后压了三个字符串进栈。这个例子也创建了一个`Array`单例,并初始化包含三个同栈里一样的原始字符串。即便栈和数组否是不同的类型,但他们都遵循`Container`协议,而且他们都包含同样的类型值。你因此可以调用`allItemsMatch`函数,用这两个容器作为它的参数。在上面的例子中,`allItemsMatch`函数正确的显示了所有的这两个容器的items匹配。 + + [1]: https://www.zybuluo.com/mdeditor?url=https://www.zybuluo.com/static/editor/md-help.markdown + [2]: https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Art/stackPushPop_2x.png + [3]: https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Art/stackPushedFourStrings_2x.png + [4]: https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Art/stackPoppedOneString_2x.png + [5]: https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/CollectionTypes.html#//apple_ref/doc/uid/TP40014097-CH8-XID_143 + [6]: https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Protocols.html#//apple_ref/doc/uid/TP40014097-CH25-XID_355 + [7]: https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Protocols.html#//apple_ref/doc/uid/TP40014097-CH25-XID_357 + [8]: https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Generics.html#//apple_ref/doc/uid/TP40014097-CH26-XID_244 + [9]: https://www.zybuluo.com/mdeditor?url=https://www.zybuluo.com/static/editor/md-help.markdown + [10]: https://www.zybuluo.com/mdeditor?url=https://www.zybuluo.com/static/editor/md-help.markdown#cmd-markdown-高阶语法手册 + [11]: http://weibo.com/ghosert + [12]: http://meta.math.stackexchange.com/questions/5020/mathjax-basic-tutorial-and-quick-reference From 637c1d14362be44e9fe881c9c6929cbcaf4630df Mon Sep 17 00:00:00 2001 From: ChildhoodAndy Date: Fri, 6 Jun 2014 18:07:13 +0800 Subject: [PATCH 3/3] delete 2-16 ARC delete 2-16 ARC --- .../16_Automatic_Reference_Counting.md | 466 ------------------ 1 file changed, 466 deletions(-) diff --git a/source/chapter2/16_Automatic_Reference_Counting.md b/source/chapter2/16_Automatic_Reference_Counting.md index 1aa6f460..e69de29b 100644 --- a/source/chapter2/16_Automatic_Reference_Counting.md +++ b/source/chapter2/16_Automatic_Reference_Counting.md @@ -1,466 +0,0 @@ -# 自动引用计数 -======================= - -Swift使用自动引用计数(ARC)来跟踪并管理应用使用的内存。大部分情况下,这意味着在Swift语言中,内存管理"仍然工作",不需要自己去考虑内存管理的事情。当实例不再被使用时,ARC会自动释放这些类的实例所占用的内存。 - -然而,在少数情况下,为了自动的管理内存空间,ARC需要了解关于你的代码片段之间关系的更多信息。本章描述了这些情况,并向大家展示如何打开ARC来管理应用的所有内存空间。 - ->注意
-引用计数只应用在类的实例。结构体(Structure)和枚举类型是值类型,并非引用类型,不是以引用的方式来存储和传递的。 - -## ARC如何工作 - -每次创建一个类的实例,ARC就会分配一个内存块,用来存储这个实例的相关信息。这个内存块保存着实例的类型,以及这个实例相关的属性的值。 - -当实例不再被使用时,ARC释放这个实例使用的内存,使这块内存可作它用。这保证了类实例不再被使用时,它们不会占用内存空间。 - -但是,如果ARC释放了仍在使用的实例,那么你就不能再访问这个实例的属性或者调用它的方法。如果你仍然试图访问这个实例,应用极有可能会崩溃。 - -为了保证不会发生上述的情况,ARC跟踪与类的实例相关的属性、常量以及变量的数量。只要有一个有效的引用,ARC都不会释放这个实例。 - -为了让这变成现实,只要你将一个类的实例赋值给一个属性或者常量或者变量,这个属性、常量或者变量就是这个实例的*强引用(strong reference)*。之所以称之为“强”引用,是因为它强持有这个实例,并且只要这个强引用还存在,就不允许销毁实例。 - -## ARC实践 - -下面的例子展示了ARC是如何工作的。本例定义了一个简单的类,类名是Person,并定义了一个名为name的常量属性: - - class Person { - let name: String - init(name: String) { - self.name = name - println("\(name) is being initialized") - } - - deinit { - println("\(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 "Jonh Appleseed is being initialized" - -注意,消息`“John Appleseed is being initialized”`在调用`Person`类的初始化函数时打印。这印证初始化确实发生了。 - -因为`Person`的实例赋值给了变量`reference1`,所以`reference1`是`Person`实例的强引用。又因为至少有这一个强引用,ARC就保证这个实例会保存在内存重而不会被销毁。 - -如果将这个`Person`实例赋值给另外的两个变量,那么将建立另外两个指向这个实例的强引用: - - reference2 = reference1 - reference3 = reference2 - -现在,这一个`Person`实例有三个强引用。 - -如果你通过赋值nil给两个变量来破坏其中的两个强引用(包括原始的引用),只剩下一个强引用,这个`Person`实例也不会被销毁: - - reference1 = nil - reference2 = nil - -直到第三个也是最后一个强引用被破坏,ARC才会销毁`Person`的实例,这时,有一点非常明确,你无法继续使用`Person`实例: - - referenece3 = nil - // 打印 “John Appleseed is being deinitialized” - - -## 类实例间的强引用环 - -在上面的例子中,ARC可以追踪`Person`实例的引用数量,并且在它不再被使用时销毁这个实例。 - -然而,我们有可能会写出这样的代码,一个类的实例永远不会有0个强引用。在两个类实例彼此保持对方的强引用,使得每个实例都使对方保持有效时会发生这种情况。我们称之为*强引用环*。 - -通过用弱引用或者无主引用来取代强引用,我们可以解决强引用环问题。在开始学习如何解决这个问题之前,理解它产生的原因会很有帮助。 - -下面的例子展示了一个强引用环是如何在不经意之间产生的。例子定义了两个类,分别叫`Person`和`Apartment`,这两个类建模了一座公寓以及它的居民: - - class Person { - let name: String - init(name: String) { self.name = name } - var apartment: Apartment? - deinit { println("\(name) is being deinitialized") } - } - - class Apartment { - let number: Int - init(number: Int) { self.number = number } - var tenant: Person? - deinit { println("Apartment #\(number) is being deinitialized") } - } - -每个`Person`实例拥有一个`String`类型的`name`属性以及一个被初始化为nil的`apartment`可选属性。`apartment`属性是可选的,因为一个人并不一定拥有一座公寓。 - -类似的,每个`Apartment`实例拥有一个`Int`类型的`number`属性以及一个初始化为nil的`tenant`可选属性。`tenant`属性是可选的,因为一个公寓并不一定有居民。 - -这两个类也都定义了初始化函数,打印消息表明这个类的实例正在被初始化。这使你能够看到`Person`和`Apartment`的实例是否像预期的那样被销毁了。 - -下面的代码片段定义了两个可选类型变量,`john`和`number73`,分别被赋值为特定的`Apartment`和`Person`的实例。得益于可选类型的优点,这两个变量初始值均为nil: - - var john: Person? - var number73: Apartment? - -现在,你可以创建特定的`Person`实例以及`Apartment`实例,并赋值给`john`和`number73`: - - jhon = Person(name: "John Appleseed") - number73 = Apartments(number: 73) - -下面的图表明了在创建以及赋值这两个实例后强引用的关系。`john`拥有一个`Person`实例的强引用,`number73`拥有一个`Apartment`实例的强引用: -![Resize icon](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Art/referenceCycle01_2x.png) - -现在你可以将两个实例关联起来,一个人拥有一所公寓,一个公寓也拥有一个房客。注意:用感叹号(!)来展开并访问可选类型的变量,只有这样这些变量才能被赋值: - - john!.apartment = number73 - number73!.tenant = john - -两个实例关联起来后,强引用关系如下图所示: -![Resize icon](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Art/referenceCycle02_2x.png) - -糟糕的是,关联这俩实例生成了一个强引用环,`Person`实例和`Apartment`实例各持有一个对方的强引用。因此,即使你破坏`john`和`number73`所持有的强引用,引用计数也不会变为0,因此ARC不会销毁这两个实例: - - john = nil - nuber73 = nil - -注意,当上面两个变量赋值为nil时,没有调用任何一个deinitializer。强引用环阻止了`Person`和`Apartment`实例的销毁,进一步导致内存泄漏。 - -此时强引用关系如下图所示: -![Resize icon](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Art/referenceCycle03_2x.png) - -`Person`和`Apartment`实例之间的强引用依然存在。 - -## 解决实例间的强引用环 -Swift提供两种方法来解决强引用环:弱引用和无主引用。 - -弱引用和无主引用允许引用环中的一个实例引用另外一个实例,但不是强引用。因此实例可以互相引用但是不会产生强引用环。 - -对于生命周期中引用会变为nil的实例,使用弱引用;对于初始化时赋值之后引用再也不会赋值为nil的实例,使用无主引用。 - -### 弱引用 - -`弱引用`不会增加实例的引用计数,因此不会阻止ARC销毁被引用的实例。这种特性使得引用不会变成强引用环。声明属性或者变量的时候,关键字`weak`表明引用为弱引用。 - -在实例的生命周期中,如果某些时候引用没有值,那么弱引用可以阻止强引用环。如果整个生命周期内引用`都有`值,那么相应的用无主引用,在`无主引用`这一章中有详细描述。在上面的`Apartment`例子中,有时一个`Apartment`实例可能没有房客,因此此处应该用弱引用。 ->注意
->弱引用只能声明为变量类型,因为运行时它的值可能改变。弱引用绝对不能声明为常量。 - -因为弱引用可以没有值,所以声明弱引用的时候必须是可选类型的。在Swift语言中,推荐用可选类型来作为可能没有值的引用的类型。 - -如前所述,弱引用不会保持实例,因此即使实例的弱引用依然存在,ARC也有可能会销毁实例,并将弱引用赋值为`nil`。你可以想检查其他的可选值一样检查弱引用是否存在,永远也不会碰到引用了也被销毁的实例的情况。 - -下面的例子和之前的`Person`和`Apartment`例子相似,除了一个重要的区别。这一次,我们声明`Apartment`的`tenant`属性为弱引用: - - class Person { - let name: String - init(name: String) { self.name = name } - var apartment: Apartment? - deinit { println("\(name) is being deinitialized") } - } - - class Apartment { - let number: Int - init(number: Int) { self.number = number } - weak var tenant: Person? - deinit { println("Apartment #\(number) is being deinitialized") } - } - -然后创建两个变量(`john`和`number73`)的强引用,并关联这两个实例: - - var john: Person? - var number73: Apartment? - - john = Person(name: "John Appleseed") - number73 = Apartment(nunber: 73) - - john!.apartment = number73 - number73!.tenant = john - -下面是引用的关系图: -![Resize icon](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Art/weakReference01_2x.png) - -`Person`的实例仍然是`Apartment`实例的强引用,但是`Apartment`实例则是`Person`实例的弱引用。这意味着当破坏`john`变量所持有的强引用后,不再存在任何`Person`实例的强引用: -![Resize icon](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Art/weakReference02_2x.png) - -既然不存在`Person`实例的强引用,那么该实例就会被销毁: - - john = nil - // 打印"John Appleseed is being deinitialized" - -只有`number73`还持有`Apartment`实例的强引用。如果你破坏这个强引用,那么也不存在`Apartment`实例的任何强引用: -![Resize icon](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Art/weakReference03_2x.png) - -这时,`Apartment`实例也被销毁: - - number73 = nil - // 打印"Apartment #73 is being deinitialized" - -上面的两段代码表明在`john`和`number73`赋值为`nil`后,`Person`和`Apartment`实例的deinitializer都打印了“销毁”的消息。这证明了引用环已经被打破了。 - -### 无主引用 -和弱引用相似,无主引用也不强持有实例。但是和弱引用不同的是,无主引用默认始终有值。因此,无主引用只能定义为非可选类型(non-optional type)。在属性、变量前添加`unowned`关键字,可以声明一个无主引用。 - -因为是非可选类型,因此当使用无主引用的时候,不需要展开,可以直接访问。不过非可选类型变量不能赋值为`nil`,因此当实例被销毁的时候,ARC无法将引用赋值为`nil`。 ->注意
-当实例被销毁后,试图访问该实例的无主引用会触发运行时错误。使用无主引用时请确保引用始终指向一个未销毁的实例。 -上面的非法操作会百分百让应用崩溃,不会发生无法预期的行为。因此,你应该避免这种情况。 - -接下来的例子定义了两个类,`Customer`和`CreditCard`,模拟了银行客户和客户的信用卡。每个类都一个属性,存储另外一个类的实例。这样的关系可能会产生强引用环。 - -`Customer`、`CreditCard`的关系和之前弱引用例子中的`Apartment`、`Person`的关系截然不同。在这个模型中,消费者不一定有信用卡,但是每张信用卡一定对应一个消费者。鉴于这种关系,`Customer`类有一个可选类型属性`card`,而`CreditCard`类的`customer`属性则是非可选类型的。 - -进一步,要创建一个`CreditCard`实例,只能通过传递`number`值和`customer`实例到定制的`CreditCard`初始化函数来完成。这样可以确保当创建`CreditCard`实例时总是有一个`customer`实例与之关联。 - -因为信用卡总是对应一个消费者,因此定义`customer`属性为无主引用,这样可以避免强引用环: - - class Customer { - let name: String - var card: CreditCard? - init(name: String) { - self.name = name - } - - deinit { println("\(name) is being deinitialized") - } - - class CreditCard { - let number: Int - unowned let customer: Customer - init(number: Int, customer: Customer) { - self.number = number - self.customer = customer - } - - deinit { println("Card #\(number) is being deinitialized") - } - -下面的代码定义了一个叫`john`的可选类型`Customer`变量,用来保存某个特定消费者的引用。因为是可变类型,该变量的初始值为`nil`: - - var john: Customer? - -现在创建一个`Customer`实例,然后用它来初始化`CreditCard`实例,并把刚创建出来的`CreditCard`实例赋值给`Customer`的`card`属性: - - john = Customer(name: "John Appleseed") - john!.card = CreditCard(number: 1234_5678_9012_3456, customer:john!) - -我们来看看此时的引用关系: -![Resize icon](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Art/unownedReference01_2x.png) - -`Customer`实例持有`CreditCard`实例的强引用,而`CreditCard`实例则持有`Customer`实例的无主引用。 - -因为`customer`的无主引用,当破坏`john`变量持有的强引用时,就没有`Customer`实例的强引用了: -![Resize icon](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Art/unownedReference02_2x.png) - -此时`Customer`实例被销毁。然后,`CreditCard`实例的强引用也不复存在,因此`CreditCard`实例也被销毁: - - john = nil - // 打印"John Appleseed is being deinitialized" - // 打印"Card #1234567890123456 is being deinitialized" - -上面的代码证明,`john`变量赋值为`nil`后,`Customer`实例和`CreditCard`实例的deinitializer方法都打印了"deinitialized"消息。 - -### 无主引用以及显式展开的可选属性 -上述的弱引用和无主引用的例子覆盖了两种常用的需要打破强引用环的应用场景。 - -`Person`和`Apartment`的例子说明了下面的场景:两个属性的值都可能是`nil`,并有可能产生强引用环。这种场景下适合使用弱引用。 - -`Customer`和`CreditCard`的例子则说明了另外的场景:一个属性可以是`nil`,另外一个属性不允许是`nil`,并有可能产生强引用环。这种场景下适合使用无主引用。 - -但是,存在第三种场景:两个属性都必须有值,且初始化完成后不能为`nil`。这种场景下,则要一个类用无主引用属性,另一个类用显式展开的可选属性。 - -这样,在初始化完成后我们可以立即访问这两个变量(而不需要可选展开),同时又避免了引用环。本节将告诉你应该如何配置这样的关系。 - -下面的例子顶一个了两个类,`Country`和`City`,都有一个属性用来保存另外的类的实例。在这个模型里,每个国家都有首都,每个城市都隶属于一个国家。所以,类`Country`有一个`capitalCity`属性,类`City`有一个`country`属性: - - class Country { - let name: String - let 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`的实例完全初始化完后(在Two-Phase Initialization),`Country`的初始化函数才能把`self`传给`City`的初始化函数。 - -为满足这种需求,通过在类型结尾处加感叹号(City!),我们声明`Country`的`capitalCity`属性为显式展开的可选类型属性。就是说,`capitalCity`属性的默认值是`nil`,不需要展开它的值(在Implicity Unwrapped Optionals中描述)就可以直接访问。 - -因为`capitalCity`默认值是`nil`,一旦`Country`的实例在初始化时给`name`属性赋值后,整个初始化过程就完成了。这代表只要赋值`name`属性后,`Country`的初始化函数就能引用并传递显式的`self`。所以,当`Country`的初始化函数在赋值`capitalCity`时,它也可以将`self`作为参数传递给`City`的初始化函数。 - -综上所述,你可以在一条语句中同时创建`Country`和`City`的实例,却不会产生强引用环,并且不需要使用感叹号来展开它的可选值就可以直接访问`capitalCity`: - - var country = Country(name: "Canada", capitalName: "Ottawa") - println("\(country.name)'s captial city is called \(country.capitalCity.name)") - // 打印"Canada's capital city is called Ottawa" - -在上面的例子中,使用显式展开的可选值满足了两个类的初始化函数的要求。初始化完成后,`capitalCity`属性就可以做为非可选值类型使用,却不会产生强引用环。 - -## 闭包产生的强引用环 - -前面我们看到了强引用环是如何产生的,还知道了如何引入弱引用和无主引用来打破引用环。 - -将一个闭包赋值给类实例的某个属性,并且这个闭包使用了实例,这样也会产生强引用环。这个闭包可能访问了实例的某个属性,例如`self.someProperty`,或者调用了实例的某个方法,例如`self.someMethod`。这两种情况都导致了闭包使用`self`,从而产生了抢引用环。 - -因为诸如类这样的闭包是*引用*类型,导致了强引用环。当你把一个闭包赋值给某个属性时,你也把一个*引用*赋值给了这个闭包。实质上,这个之前描述的问题是一样的-两个强引用让彼此一直有效。但是,和两个类实例不同,这次一个是类实例,另一个是闭包。 - -Swift提供了一种优雅的方法来解决这个问题,我们称之为*闭包占用列表(closuer 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)" - } else { - return "<\(self.name) />" - } - } - - init(name: String, text: String? = nil) { - self.name = name - self.text = text - } - - deinit { - println("\(name) is being deinitialized") - } - - } - -类`HTMLElement`定义了一个`name`属性来表示这个元素的名称,例如代表段落的`"p"`,或者代表换行的`"br"`;以及一个可选属性`text`,用来设置HTML元素的文本。 - -除了上面的两个属性,`HTMLElement`还定义了一个lazy属性`asHTML`。这个属性引用了一个闭包,将`name`和`text`组合成HTML字符串片段。该属性是`() -> String`类型,就是“没有参数,返回`String`的函数”。 - -默认将闭包赋值给了`asHTML`属性,这个闭包返回一个代表HTML标签的字符串。如果`text`值存在,该标签就包含可选值`text`;或者不包含文本。对于段落,根据`text`是`"some text"`还是`nil`,闭包会返回`"

some text

"`或者`"

"`。 - -可以像实例方法那样去命名、使用`asHTML`。然而,因为`asHTML`终究是闭包而不是实例方法,如果你像改变特定元素的HTML处理的话,可以用定制的闭包来取代默认值。 ->注意
-`asHTML`声明为lazy属性,因为只有当元素确实需要处理为HTML输出的字符串时,才需要使用`asHTML`。也就是说,在默认的闭包中可以使用`self`,因为只有当初始化完成以及`self`确实存在后,才能访问lazy属性。 - -`HTMLElement`只有一个初始化函数,根据`name`和`text`(如果有的话)参数来初始化一个元素。该类也定义了一个deinitializer,当`HTMLElement`实例被销毁时,打印一条消息。 - -下面的代码创建一个`HTMLElement`实例并打印消息。 - - var paragraph: HTMLElement? = HTMLElement(name: "p", text: "hello, world") - println(paragraph!.asHTML()) - // 打印"

hello, world

" - ->注意
-上面的`paragraph`变量定义为可选`HTMLElement`,因此我们可以赋值`nil`给它来演示强引用环。 - -不幸的是,`HTMLElement`类产生了类实例和`asHTML`默认值的闭包之间的强引用环。如下图所示: - -![Resize icon](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Art/closureReferenceCycle01_2x.png) - -实例的`asHTML`属性持有闭包的强引用。 - -但是,闭包使用了`self`(引用了`self.name`和`self.text`),因此闭包*占有*了`self`,这意味着闭包又反过来持有了`HTMLElement`实例的强引用。这样就产生了强引用环。(更多闭包哪占有值的信息,请参考Capturing Values)。 - ->注意
-虽然闭包多次使用了`self`,它只占有`HTMLElement`实例的一个强引用。 - -如果设置`paragraph`为`nil`,打破它持有的`HTMLElement`实例的强引用,`HTMLElement`实例和它的闭包都不会被销毁,就因为强引用环: - - paragraph = nil - -注意`HTMLElement`deinitializer中的消息并没有别打印,印证了`HTMLElement`实例并没有被销毁。 - - -## 解决闭包产生的强引用环 - -在定义闭包时同时定义*占有列表*作为闭包的一部分,可以解决闭包和类实例之间的强引用环。占有列表定义了闭包内占有一个或者多个引用类型的规则。和解决两个类实例间的强引用环一样,声明每个占有的引用为弱引用或无主引用,而不是强引用。根据代码关系来决定使用弱引用还是无主引用。 - ->注意
-Swift有如下约束:只要在闭包内使用`self`的成员,就要用`self.someProperty`或者`self.someMethod`(而非只是`someProperty`或`someMethod`)。这可以提醒你可能会不小心就占有了`self`。 - -### 定义占有列表 - -占有列表中的每个元素都是由`weak`或者`unowned`关键字和实例的引用(如`self`或`someInstance`)组成。每一对都在花括号中,通过逗号分开。 - -占有列表放置在闭包参数列表和返回类型之前: - - @lazy var someClosure: (Int, String) -> String = { - [unowned self] (index: Int, stringToProcess: String) -> String in - // closure body goes here - } - -如果闭包没有指定参数列表或者返回类型(可以通过上下文推断),那么占有列表放在闭包开始的地方,跟着是关键字`in`: - - @lazy var someClosure: () -> String = { - [unowned self] 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)" - } else { - return "<\(self.name) />" - } - } - - init(name: String, text: String? = nil) { - self.name = name - self.text = text - } - - deinit { - println("\(name) is being deinitialized") - } - - } - -上面的`HTMLElement`实现和之前的实现相同,只是多了占有列表。这里,占有列表是`[unowned self]`,代表“用无主引用而不是强引用来占有`self`”。 - -和之前一样,我们可以创建并打印`HTMLElement`实例: - - var paragraph: HTMLElement? = HTMLElement(name: "p", text: "hello, world") - println(paragraph!.asTHML()) - // 打印"

hello, world

" - -使用占有列表后引用关系如下图所示: - -![Resize icon](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Art/closureReferenceCycle02_2x.png) - -这一次,闭包以无主引用的形式占有`self`,并不会持有`HTMLElement`实例的强引用。如果赋值`paragraph`为`nil`,`HTMLElement`实例将会被销毁,并能看到它的deinitializer打印的消息。 - - paragraph = nil - // 打印"p is being deinitialized"