您的位置:首页 > 移动开发 > Swift

(译)Swift2.2-可选链

2016-03-21 15:01 387 查看
可选链(Optional Chaining)是为了在一个可能当前值为
nil
optional
类型里,查询和调用属性,方法和下标脚本的一个过程。如果这个可选类型包含了一个值,属性,方法或是下标脚本,那么就会调用成功;如果这个可选类型为
nil
,那么属性,方法或下表脚本调用返回值就为
nil
。多个连续的调用可以被链接在一起,但是如果在这个链接里有的节点为
nil
,那么就会导致整个链接失败。

注意:
在Swift中,可选链和Objective-C中消息为`nil`有些类似,但是Swift可以使用在任何类型中,并且可以检查调用是否成功。


使用可选链调用来强制展开

你可以在你希望调用的属性,方法或者下标脚本后面,如果这些值为非
nil
,那么你可以在可选值的后面使用一个问号(?)来替代可选链。这和在可选值后面放一个感叹号(?),强制解包有些类似。主要的不同就是可选链会在可选值为
nil
的调用失败,因为强制解包会在可选值为
nil
的时候触发运行时错误。

为了反应可选链可以被一个
nil
值调用,可选链调用的结果总是可选值,不论这个属性,方法或下标脚本返回的是不是非可选值。你可以使用这个可选返回值来检查可选链调用成功(返回的可选变量包含一个值),或者由于在链接里有一个
nil
值就会调用失败。

特别地,可选链地调用的结果与原本烦人返回结果有相同的类型,但是包装成了一个可选类型。当通过可选链的方式,一个
Int
型的属性会返回一个
Int?


下面的代码片段解释了可选链调用和强制展开的不同。

首先,声明了两个类,分别为
Person
Residence


class Person {
var residence: Residence?
}
class Residence {
var numberOfRooms = 1
}


Residence
市实例有一个名为
numberOfRooms
Int
类型的属性,有一个默认值1。
Person
实例有一个可选的
residence
类型
Residence?


如果你创建了一个新的
Person
实例,它的
residence
属性默认初始化为
nil
,在下面的代码,
john
有一个
residence
属性为
nil


let john = Person()


如果你想访问这个
person
residence
numberOfRooms
属性,可以在
residence
后面加一个感叹号来强制解包它的值,那么你就会触发一个运行时错误,因为没有可展开的
residence


let roomCount = john.residence!.numberOfRooms
// this triggers a runtime error


上面的代码如果改成
john.residence
有一个非
nil
值就会调用成功。并且设置一个
Int
型的
roomCount
存储房间的数量。但是,当
residence
为空的时候上面这段代码会触发运行时错误。

可选链调用提供了一种到另一种访问
numberOfRooms
的方法,使用问号(?)来代替原来叹号(!)的位置:

if let roomCount = john.residence?.numberOfRooms {
print("John's residence has \(roomCount) room(s).")
} else {
print("Unable to retrieve the number of rooms.")
}
// Prints "Unable to retrieve the number of rooms."


residence
后面添加问号之后,Swift就会在
residence
不为空的情况下访问
numberOfRooms


因为访问
numberOfRooms
有可能失败,可选链会返回
Int?
类型,或称为“可空的
Int
”。如上例所示,当
residence
nil
的时候,可空的
Int
将会为
nil
,表明无法访问
numberOfRooms


要注意的是,即使
numberOfRooms
是不可空的
Int
时,这一点也成立。只要是通过可选链,就意味着最后
numberOfRooms
返回一个
Int?
而不是
Int


通过赋给
john.residence
一个
Residence
的实例变量,这样
john.residence
不为
nil
了:

john.residence = Residence()


现在就可以正常访问
john.residence.numberOfRooms
,其值为默认的1,类型为
Int?


if let roomCount = john.residence?.numberOfRooms {
print("John's residence has \(roomCount) room(s).")
} else {
print("Unable to retrieve the number of rooms.")
}
// Prints "John's residence has 1 room(s)."


为可选链定义模型类

通过使用可选链可以调用多层属性,方法,和下标脚本。这样可以通过各种模型向下访问各种子属性。并且判断能否访问子属性的属性,方法或下标。

下面这段代码定义了四个模型类,这些例子包括多层可空链式调用。为了方便说明,在
Person
Residence
的基础上增加了
Room
Address
,以及相关的属性,方法以及下标。

class Person {
var residence: Residence?
}


Residence
类也比之前更完整。这次,
Residence
类定义一个名为
rooms
的变量属性,初始化为[Room]类型的空数组。

class Residence {
var rooms = [Room]()
var numberOfRooms: Int {
return rooms.count
}
subscript(i: Int) -> Room {
get {
return rooms[i]
}
set {
rooms[i] = newValue
}
}
func printNumberOfRooms() {
print("The number of rooms is \(numberOfRooms)")
}
var address: Address?
}
class Residence {
var rooms = [Room]()
var numberOfRooms: Int {
return rooms.count
}
subscript(i: Int) -> Room {
get {
return rooms[i]
}
set {
rooms[i] = newValue
}
}
func printNumberOfRooms() {
print("The number of rooms is \(numberOfRooms)")
}
var address: Address?
}


现在
Residence
有了一个存储
Room
类型的数组,
numberOfRooms
属性需要计算,而不是作为单纯的变量。计算后的
numberOfRooms
返回
rooms
数组的
count
属性值。现在的
Residence
还提供访问
rooms
数组的快捷方式, 通过可读写的下标来访问指定位置的数组元素。此外,还提供
printNumberOfRooms
方法,这个方法的作用就是输出这个房子中房间的数量。最后,
Residence
定义了一个可空属性
address
,其类型为
Address?
Address
类的定义在下面会说明。

Room
类被用在
rooms
数组里,它是一个简单类有一个名为
name
的属性,并且有一个初始化器来设置房间的名字

class Room {
let name: String
init(name: String) { self.name = name }
}


最后一个类是
Address
,这个类有三个
String?
类型的可空属性。
buildingName
以及
buildingNumber
属性表示建筑的名称和号码,用来表示某个特定的建筑。第三个属性表示建筑所在街道的名称:

class Address {
var buildingName: String?
var buildingNumber: String?
var street: String?
func buildingIdentifier() -> String? {
if buildingName != nil {
return buildingName
} else if buildingNumber != nil && street != nil {
return "\(buildingNumber) \(street)"
} else {
return nil
}
}
}


Address
提供
buildingIdentifier()
方法,返回值为
String?
。 如果
buildingName
不为空则返回
buildingName
, 如果
buildingNumber
不为空则返回
buildingNumber
。如果这两个属性都为空则返回
nil


通过可选链访问属性

正如上文使用可选链来强制展开中所述,可以通过可空链式调用访问属性的可空值,并且判断访问是否成功。

上面使用类定义来创建一个新的
Person
实例,然后访问
numberOfRooms
属性:

let john = Person()
if let roomCount = john.residence?.numberOfRooms { print("John's residence has \(roomCount) room(s).") } else { print("Unable to retrieve the number of rooms.") } // Prints "Unable to retrieve the number of rooms."


因为
john.residence
nil
,所以毫无疑问这个可空链式调用失败。

通过可空链式调用来设定属性值:

let someAddress = Address()
someAddress.buildingNumber = "29"
someAddress.street = "Acacia Road"
john.residence?.address = someAddress


在这个例子中,通过
john.residence
来设定
address
属性也是不行的,因为
john.residence
nil


func createAddress() -> Address {
print("Function was called.")
let someAddress = Address()
someAddress.buildingNumber = "29"
someAddress.street = "Acacia Road"
return someAddress
}
john.residence?.address = createAddress()


你可以使用
createAddress()
函数,就就会知道它没有被调用,因为什么都没有打印。

通过可选链来调用方法

可以通过可空链式调用来调用方法,并判断是否调用成功,即使这个方法没有返回值。

Residence
中的
printNumberOfRooms()
方法输出当前的
numberOfRooms
值:

func printNumberOfRooms() {
print("The number of rooms is \(numberOfRooms)")
}


这个方法没有返回值。但是没有返回值的方法隐式返回
Void
类型,如无返回值函数中所述。这意味着没有返回值的方法也会返回()或者空的元组。

如果在可空值上通过可空链式调用来调用这个方法,这个方法的返回类型为
Void?
,而不是
Void
,因为通过可空链式调用得到的返回值都是可空的。这样我们就可以使用
if
语句来判断能否成功调用
printNumberOfRooms()
方法,即使方法本身没有定义返回值。通过返回值是否为
nil
可以判断调用是否成功:

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.")
}
// Prints "It was not possible to print the number of rooms."


同样的,可以判断通过可空链式调用来给属性赋值是否成功。在上面的例子中,我们尝试给
john.residence
中的
address
属性赋值,即使
residence
nil
。通过可空链式调用给属性赋值会返回
Void?
,通过判断返回值是否为
nil
可以知道赋值是否成功:

if (john.residence?.address = someAddress) != nil {
print("It was possible to set the address.")
} else {
print("It was not possible to set the address.")
}
// Prints "It was not possible to set the address."


通过可选链来访问下标脚本

通过可空链式调用,我们可以用下标来对可空值进行读取或写入,并且判断下标调用是否成功。

注意:
当通过可空链式调用访问可空值的下标的时候,应该将问号放在下标方括号的前面而不是后面。可空链式调用的问号一般直接跟在可空表达式的后面。


下面这个例子用下标访问
john.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.")
}
// Prints "Unable to retrieve the first room name."


在这个例子中,问号直接放在
john.residence
的后面,并且在方括号的前面,因为
john.residence
是可空值。

类似的,可以通过下标,用可空链式调用来赋值:

john.residence?[0] = Room(name: "Bathroom")


这次赋值同样会失败,因为
residence
目前是
nil


如果你创建一个
Residence
实例,添加一些
Room
实例并赋值给
john.residence
,那就可以通过可选链和下标来访问数组中的元素:

let johnsHouse = Residence()
johnsHouse.rooms.append(Room(name: "Living Room"))
johnsHouse.rooms.append(Room(name: "Kitchen"))
john.residence = johnsHouse
if let firstRoomName = john.residence?[0].name {
print("The first room name is \(firstRoomName).")
} else {
print("Unable to retrieve the first room name.")
}
// Prints "The first room name is Living Room."


访问可选类型的下标脚本

如果一个下标脚本返回了一个可选类型–例如Swift的
Dictionary
类型–在下标右中括号前加一个问号:

var testScores = ["Dave": [86, 82, 84], "Bev": [79, 94, 81]]
testScores["Dave"]?[0] = 91
testScores["Bev"]?[0] += 1
testScores["Brian"]?[0] = 72
// the "Dave" array is now [91, 82, 84] and the "Bev" array is now [80, 94, 81]


上面的例子中定义了一个
testScores
数组,包含了两个键值对, 把
String
类型的key映射到一个整形数组。这个例子用可空链式调用把
“Dave”
数组中第一个元素设为
91
,把
”Bev”
数组的第一个元素
+1
,然后尝试把
”Brian”
数组中的第一个元素设为
72
。前两个调用是成功的,因为这两个key存在。但是key
“Brian”
在字典中不存在,所以第三个调用失败。

多层链接

可以通过多个链接多个可空链式调用来向下访问属性,方法以及下标。但是多层可空链式调用不会添加返回值的可空性。

也就是说:

如果你访问的值不是可空的,通过可空链式调用将会放回可空值。

如果你访问的值已经是可空的,通过可空链式调用不会变得“更”可空。

因此:

通过可空链式调用访问一个
Int
值,将会返回
Int?
,不过进行了多少次可空链式调用。

类似的,通过可空链式调用访问
Int?
值,并不会变得更加可空。

if let johnsStreet = john.residence?.address?.street {
print("John's street name is \(johnsStreet).")
} else {
print("Unable to retrieve the address.")
}
// Prints "Unable to retrieve the address."


john.residence
包含
Residence
实例,但是
john.residence.address
nil
。因此,不能访问
john.residence?.address?.street


需要注意的是,上面的例子中,
street
的属性为
String?
john.residence?.address?.street
的返回值也依然是
String?
,即使已经进行了两次可空的链式调用。

如果把
john.residence.address
指向一个实例,并且为
address
中的
street
属性赋值,我们就能过通过可空链式调用来访问street属性

如果你设置一个
Address
实例作为
john.residence.address
,并且为地址的
street
属性设置一个实际的值,你可以通过多层链接访问
street
属性的值:

let johnsAddress = Address()
johnsAddress.buildingName = "The Larches"
johnsAddress.street = "Laurel Street"
john.residence?.address = johnsAddress
if let johnsStreet = john.residence?.address?.street {
print("John's street name is \(johnsStreet).")
} else {
print("Unable to retrieve the address.")
}
// prints "John's street name is Laurel Street."


在上面的例子中,因为
john.residence
是一个可用的
Residence
实例,所以对
john.residence
address
属性赋值成功。

对返回可空值的函数进行链接

上面的例子说明了如何通过可空链式调用来获取可空属性值。我们还可以通过可空链式调用来调用返回可空值的方法,并且可以继续对可空值进行链接。

在下面的例子中,通过可空链式调用来调用
Address
buildingIdentifier()
方法。这个方法返回
String?
类型。正如上面所说,通过可空链式调用的方法的最终返回值还是
String?


if let buildingIdentifier = john.residence?.address?.buildingIdentifier() {
print("John's building identifier is \(buildingIdentifier).")
}
// Prints "John's building identifier is The Larches."


如果要进一步对方法的返回值进行可空链式调用,在方法
buildingIdentifier()
的圆括号后面加上问号:

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\".")
}
}
// Prints "John's building identifier begins with "The"."


注意:
在上面的例子中在,在方法的圆括号后面加上问号是因为buildingIdentifier()的返回值是可空值,而不是方法本身是可空的。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  swift