Swift 使用自动引用计数(ARC)机制来跟踪和管理你的应用程序的内存。通常情况下,Swift 内存管理机制会一直起作用,你无须自己来考虑内存的管理。ARC 会在类的实例不再被使用时,自动释放其占用的内存。
1 自动引用计数的工作机制
1.1 自动引用计数的工作机制
<1>当你每次创建一个类的新的实例的时候,ARC 会分配一块内存来储存该实例信息。内存中会包含实例的类型信息,以及这个实例所有相关的存储型属性的值。
<2>当实例不再被使用时,ARC 释放实例所占用的内存,并让释放的内存能挪作他用。这确保了不再被使用的实例,不会一直占用内存空间。
<3>为了确保使用中的实例不会被销毁,ARC 会跟踪和计算每一个实例正在被多少属性,常量和变量所引用。哪怕实例的引用数为1,ARC都不会销毁这个实例。
1.2 类实例之间的循环强引用
如果两个类实例互相持有对方的强引用,因而每个实例都让对方一直存在,就是这种情况。这就是所谓的循环强引用。
1.3 解决实例之间的循环强引用
Swift 提供了两种办法用来解决你在使用类的属性时所遇到的循环强引用问题:弱引用(weak reference)和无主引用(unowned reference)。
1.3.1 弱引用
弱引用不会对其引用的实例保持强引用,因而不会阻止 ARC 销毁被引用的实例。这个特性阻止了引用变为循环强引用。声明属性或者变量时,在前面加上weak 关键字表明这是一个弱引用。
注意
当 ARC 设置弱引用为nil 时,属性观察不会被触发。
注意
在使用垃圾收集的系统里,弱指针有时用来实现简单的缓冲机制,因为没有强引用的对象只会在内存压力触发垃圾收集时才被销毁。但是在 ARC 中,一旦值的最后一个强引用被移除,就会被立即销毁,这导致弱引用并不适
合上面的用途。
1.3.2 无主引用
和弱引用类似,无主引用不会牢牢保持住引用的实例。和弱引用不同的是,无主引用在其他实例有相同或者更长的生命周期时使用。你可以在声明属性或者变量时,在前面加上关键字unowned 表示这是一个无主引用。
无主引用通常都被期望拥有值。不过 ARC 无法在实例被销毁后将无主引用设为nil ,因为非可选类型的变量不允许被赋值为nil 。
重要
使用无主引用,你必须确保引用始终指向一个未销毁的实例。
如果你试图在实例被销毁后,访问该实例的无主引用,会触发运行时错误。
1.3.3 无主引用以及隐式解析可选属性
存在着第三种场景,在这种场景中,两个属性都必须有值,并且初始化完成后永远不会为nil 。在这种场景中,需要一个类使用无主属性,而另外一个类使用隐式解析可选属性。这使两个属性在初始化完成后能被直接访问(不需要可选展开),同时避免了循环引用。
1.4 闭包引起的循环强引用
循环强引用还会发生在当你将一个闭包赋值给类实例的某个属性,并且这个闭包体中又使用了这个类实例时。这个闭包体中可能访问了实例的某个属性,例如self.someProperty ,或者闭包中调用了实例的某个方法,例如self.someMethod() 。这两种情况都导致了闭包“捕获” self ,从而产生了循环强引用。
注意
虽然闭包多次使用了self ,它只捕获HTMLElement 实例的一个强引用。
1.5 解决闭包引起的循环强引用
在定义闭包时同时定义捕获列表作为闭包的一部分,通过这种方式可以解决闭包和类实例之间的循环强引用。捕获列表定义了闭包体内捕获一个或者多个引用类型的规则。跟解决两个类实例间的循环强引用一样,声明每个捕获的引用为弱引用或无主引用,而不是强引用
1.5.1 定义捕获列表
捕获列表中的每一项都由一对元素组成,一个元素是weak 或unowned 关键字,另一个元素是类实例的引用(例如self )或初始化过的变量(如delegate = self.delegate! )。这些项在方括号中用逗号分开。
如果闭包有参数列表和返回类型,把捕获列表放在它们前面:
[unowned self, weak delegate = self.delegate!] (index: Int, stringToProcess: String) -> String in
// 这里是闭包的函数体
}
如果闭包没有指明参数列表或者返回类型,即它们会通过上下文推断,那么可以把捕获列表和关键字in 放在闭包最开始的地方:
lazy var someClosure: Void -> String = {
[unowned self, weak delegate = self.delegate!] in
// 这里是闭包的函数体
}
1.5.2 弱引用和无主引用
在闭包和捕获的实例总是互相引用并且总是同时销毁时,将闭包内的捕获定义为无主引用。
相反的,在被捕获的引用可能会变为nil 时,将闭包内的捕获定义为弱引用。弱引用总是可选类型,并且当引用的实例被销毁后,弱引用的值会自动置为nil 。这使我们可以在闭包体内检查它们是否存在。
注意
如果被捕获的引用绝对不会变为nil ,应该用无主引用,而不是弱引用。
2 可选链式调用
可选链式调用是一种可以在当前值可能为nil 的可选值上请求和调用属性、方法及下标的方法。如果可选值有值,那么调用就会成功;如果可选值是nil ,那么调用将返回nil 。多个调用可以连接在一起形成一个调用链,如果其中任何一个节点为nil ,整个调用链都会失败,即返回nil 。
注意
Swift 的可选链式调用和 Objective-C 中向nil 发送消息有些相像,但是 Swift 的可选链式调用可以应用于任意类型,并且能检查调用是否成功。
2.1 使用可选链式调用代替强制展开
通过在想调用的属性、方法、或下标的可选值后面放一个问号( ? ),可以定义一个可选链。这一点很像在可选值后面放一个叹号( ! )来强制展开它的值。它们的主要区别在于当可选值为空时可选链式调用只会调用失败,然而强制展开将会触发运行时错误。
if let roomCount = john.residence?.numberOfRooms {
print("John's residence has \(roomCount) room(s).")
} else {
print("Unable to retrieve the number of rooms.")
}
// 打印 “Unable to retrieve the number of rooms.”
2.2 为可选链式调用定义模型类
通过使用可选链式调用可以调用多层属性、方法和下标。这样可以在复杂的模型中向下访问各种子属性,并且判断能否访问子属性的属性、方法或下标。
2.3 通过可选链式调用访问属性
可以通过可选链式调用在一个可选值上访问它的属性,并判断访问是否成功。
let someAddress = Address()
someAddress.buildingNumber = "29"
someAddress.street = "Acacia Road"
john.residence?.address = someAddress
//在这个例子中,通过john.residence 来设定address 属性也会失败,因为john.residence 当前为nil
2.4 通过可选链式调用调用方法
可以通过可选链式调用来调用方法,并判断是否调用成功,即使这个方法没有返回值。
if john.residence?.printNumberOfRooms() != nil {
print("It was possible to print the number of rooms.")
} else {
print("It was not possible to print the number of rooms.")
}
// 打印 “It was not possible to print the number of rooms.”
2.5 通过可选链式调用访问下标
通过可选链式调用,我们可以在一个可选值上访问下标,并且判断下标调用是否成功。
注意
通过可选链式调用访问可选值的下标时,应该将问号放在下标方括号的前面而不是后面。可选链式调用的问号一般直接跟在可选表达式的后面。
下面这个例子用下标访问john.residence 属性存储的Residence 实例的rooms 数组中的第一个房间的名称,因为john.residence 为nil ,所以下标调用失败了:
if let firstRoomName = john.residence?[0].name {
print("The first room name is \(firstRoomName).")
} else {
print("Unable to retrieve the first room name.")
}
// 打印 “Unable to retrieve the first room name.”
2.5.1 访问可选类型的下标
如果下标返回可选类型值,比如 Swift 中Dictionary 类型的键的下标,可以在下标的结尾括号后面放一个问号来在其可选返回值上进行可选链式调用:
var testScores = ["Dave": [86, 82, 84], "Bev": [79, 94, 81]]
testScores["Dave"]?[0] = 91
testScores["Bev"]?[0] += 1
testScores["Brian"]?[0] = 72
// "Dave" 数组现在是 [91, 82, 84],"Bev" 数组现在是 [80, 94, 81]
2.6 连接多层可选链式调用
可以通过连接多个可选链式调用在更深的模型层级中访问属性、方法以及下标。然而,多层可选链式调用不会增加返回值的可选层级
• 如果你访问的值不是可选的,可选链式调用将会返回可选值
• 如果你访问的值就是可选的,可选链式调用不会让可选返回值变得“更可选”。
if let johnsStreet = john.residence?.address?.street {
print("John's street name is \(johnsStreet).")
} else {
print("Unable to retrieve the address.")
}
// 打印 “Unable to retrieve the address.”
2.7 在方法的可选返回值上进行可选链式调用
我们还可以在一个可选值上通过可选链式调用来调用方法,并且可以根据需要继续在方法的可选返回值上进行可选链式调用。
if let buildingIdentifier = john.residence?.address?.buildingIdentifier() {
print("John's building identifier is \(buildingIdentifier).")
}
// 打印 “John's building identifier is The Larches.”
如果要在该方法的返回值上进行可选链式调用,在方法的圆括号后面加上问号即可:
if let beginsWithThe = john.residence?.address?.buildingIdentifier()?.hasPrefix("The") {
if beginsWithThe {
print("John's building identifier begins with \"The\".")
} else {
print("John's building identifier does not begin with \"The\".")
}
}
// 打印 “John's building identifier begins with "The".”
注意
在上面的例子中,在方法的圆括号后面加上问号是因为你要在buildingIdentifier() 方法的可选返回值上进行可选链式调用,而不是方法本身。