Finish ARC part

This commit is contained in:
Timothy
2014-06-09 13:24:04 +08:00
parent 73db7075f1
commit aafea0c811

View File

@ -325,4 +325,148 @@ Country的构造函数调用了City的构造函数。然而只有Country的
在上面的例子中使用显示展开可选值的意义在于满足了两个类构造函数的需求。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)</\(self.name)>"
} 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"。HTMLElement还定义了一个可选属性text用来设置和展现HTML元素的文本。
除了上面的两个属性HTMLElement还定义了一个lazy属性asHTML。这个属性引用了一个闭包将name和text组合成HTML字符串片段。该属性是() -> String类型或者可以理解为“一个没有参数返回String的函数”。
默认情况下闭包赋值给了asHTML属性这个闭包返回一个代表HTML标签的字符串。如果text值存在该标签就包含可选值text如果text不存在该标签就不包含文本。对于段落元素根据text是"some text"还是nil闭包会返回"`<p>some text</p>`"或者"`<p />`"。
可以像实例方法那样去命名、使用asHTML属性。然而由于asHTML是闭包而不是实例方法如果你想改变特定元素的HTML处理的话可以用自定义的闭包来取代默认值。
> 注意: asHTML声明为lazy属性因为只有当元素确实需要处理为HTML输出的字符串时才需要使用asHTML。也就是说在默认的闭包中可以使用self因为只有当初始化完成以及self确实存在后才能访问lazy属性。
HTMLElement类只提供一个构造函数通过name和text(如果有的话)参数来初始化一个元素。该类也定义了一个析构函数当HTMLElement实例被销毁时打印一条消息。
下面的代码展示了如何用HTMLElement类创建实例并打印消息。
var paragraph: HTMLElement? = HTMLElement(name: "p", text: "hello, world")
println(paragraph!.asHTML())
// prints"hello, world"
>注意: 上面的paragraph变量定义为可选HTMLElement因此我们可以赋值nil给它来演示循环强引用。
不幸的是上面写的HTMLElement类产生了类实例和asHTML默认值的闭包之间的循环强引用。循环强引用如下图所示
![](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](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Closures.html#//apple_ref/doc/uid/TP40014097-CH11-XID_129))。
>注意: 虽然闭包多次使用了self它只占有HTMLElement实例的一个强引用。
如果设置paragraph变量为nil打破它持有的HTMLElement实例的强引用HTMLElement实例和它的闭包都不会被销毁也是因为循环强引用
paragraph = nil
注意HTMLElementdeinitializer中的消息并没有别打印证明了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)</\(self.name)>"
} else {
return "<\(self.name) />"
}
}
init(name: String, text: String? = nil) {
self.name = name
self.text = text
}
deinit {
println("\(name) is being deinitialized")
}
}
上面的HTMLElement实现和之前的实现一致只是在asHTML闭包中多了一个占有列表。这里占有列表是[unowned self]表示“用无主引用而不是强引用来占有self”。
和之前一样我们可以创建并打印HTMLElement实例
var paragraph: HTMLElement? = HTMLElement(name: "p", text: "hello, world")
println(paragraph!.asHTML())
// prints "<p>hello, world</p>"
使用占有列表后引用关系如下图所示:
![](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Art/closureReferenceCycle02_2x.png)
这一次闭包以无主引用的形式占有self并不会持有HTMLElement实例的强引用。如果将paragraph赋值为nilHTMLElement实例将会被销毁并能看到它的析构函数打印出的消息。
paragraph = nil
// prints "p is being deinitialized"