Optional chaining is a process for querying and calling properties, methods, and subscripts on an optional that might currently be nil. If the optional contains a value, the property, method, or subscript call succeeds; if the optional is nil, the property, method, or subscript call returns nil. Multiple queries can be chained together, and the entire chain fails gracefully if any link in the chain is nil.
可选择链是一种可以对一个可选择类型进行请求和调用属性,方法,订阅的过程,此可选类型的当前值可以为空。如果可选类型包含有值,则属性,方法或脚本的调用就成功;如果可选类型的值为nil,则属性,方法或脚本的调用返回nil。多个请求可以链接在一起,如果此链接中有一个链接为nil,则整个链接都失败。
可选择链可替代强制拆包
You specify optional chaining by placing a question mark (?) after the optional value on which you wish to call a property, method or subscript if the optional is non-nil. This is very similar to placing an exclamation mark (!) after an optional value to force the unwrapping of its value. The main difference is that optional chaining fails gracefully when the optional is nil, whereas forced unwrapping triggers a runtime error when the optional is nil.
在那个你想用来调用属性,方法或脚本的可选类型(如果该选择类型是非空的话)后加上一个问号(?),即可标记为可选择链。这根在可选类型后门加上感叹号来强制拆包是类似的。主要区别是:可选择链在可选类型为nil时就会失败,而强制拆包在可选类型为nil时会触发一个runtime错误。
To reflect the fact that optional chaining can be called on a nil value, the result of an optional chaining call is always an optional value, even if the property, method, or subscript you are querying returns a nonoptional value. You can use this optional return value to check whether the optional chaining call was successful (the returned optional contains a value), or did not succeed due to a nil value in the chain (the returned optional value is nil).
为了表明可选择链可以在一个nil值上调用,可选择链的结果永远是一个可选类型,即使你请求的属性,方法或脚本返回一个非可选的值。你可以使用这个可选的返回值来检查可选择链的调用是成功的(返回的可选类型保护一个值)还是不成功的(返回的可选类型值为nil).
Specifically, the result of an optional chaining call is of the same type as the expected return value, but wrapped in an optional. A property that normally returns an Int will return an Int? when accessed through optional chaining.
有一种特殊情况,可选择链调用的结果跟预期的返回值的类型一致,但是被装箱为可选的。当通过可选择链调用一个原本自动返回int的属性时,此属性会返回int?。
看个例子:
class Person {
var residence: Residence?
}
class Residence {
var numberOfRooms = 1
}
Residence instances have a single Int property called numberOfRooms, with a default value of 1. Person instances have an optional residence property of type Residence?.
Residence实例有一个int属性numberOfRooms,默认值为1.Person实例有一个可选的residence属性,类型是Residence?.
If you create a new Person instance, its residence property is default initialized to nil, by virtue of being optional. In the code below, john has a residence property value of nil:
如果你创建一个新的Person实例,它的residence属性会默认初始为nil。在下面的代码中,john有一个值为nil的residence属性:
let john = Person()
If you try to access the numberOfRooms property of this person’s residence, by placing an exclamation mark after residence to force the unwrapping of its value, you trigger a runtime error, because there is no residence value to unwrap:
如果你想获取这个人的residence的numberOfRooms属性,需要在residence后门加上感叹号来强制拆包,此时会触发一个运行时错误,因为residence的值为nil,无法拆包:
let roomCount = john.residence!.numberOfRooms
// 这会触发一个运行时错误
The code above succeeds when john.residence has a non-nil value and will set roomCount to an Int value containing the appropriate number of rooms. However, this code always triggers a runtime error when residence is nil, as illustrated above.
当 john.residence为一个非空值(non-nil value)并且把roomCount设为一个包含适当的int值的时候,上面的代码才会成功。但是,residence为空时,上面的代码就总是触发一个运行时错误。
Optional chaining provides an alternative way to access the value of numberOfRooms. To use optional chaining, use a question mark in place of the exclamation mark:
可选择链提供了一种替代的方式来获取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."
This tells Swift to “chain” on the optional residence property and to retrieve the value of numberOfRooms if residence exists.
这段代码就是告诉swift在可选的residence属性上进行“链接”并获取numberOfRooms的值,如果residence存在的话。
Because the attempt to access numberOfRooms has the potential to fail, the optional chaining attempt returns a value of type Int?, or “optional Int”. When residence is nil, as in the example above, this optional Int will also be nil, to reflect the fact that it was not possible to access numberOfRooms.
由于获取numberOfRooms的值有可能会失败,可选择链返回的值的类型是Int?。当residence为nil时,则这个int?就返回nil,表示它无法获取numberOfRooms的值。
You can assign a Residence instance to john.residence, so that it no longer has a nil value:
你可以把一个Residence实例赋给 john.residence,此时 john.residence不再是nil:
john.residence = Residence()
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)."
为可选择链定义模型类
You can use optional chaining with calls to properties, methods, and subscripts that are more than one level deep. This enables you to drill down into subproperties within complex models of interrelated types, and to check whether it is possible to access properties, methods, and subscripts on those subproperties.
可选择链可以调用不止一层的属性,方法和脚本。这使得你可以调用子属性,并检查此子属性的属性,方法和脚本是否能获取。
The code snippets below define four model classes for use in several subsequent examples, including examples of multilevel optional chaining. These classes expand upon the Person and Residence model from above by adding a Room and Address class, with associated properties, methods, and subscripts.
下面的代码定义了四个模型类,可供下面的几个例子使用,包括多层可选择链的例子。这些类基于上面的Person和Residence模型进行了拓展,增加了Room和Address类以及相关的属性,方法和脚本。
class Person {
var residence: Residence?
}
The Residence class is more complex than before. This time, the Residence class defines a variable property called rooms, which is initialized with an empty array of type [Room]:
Residence 类就更复杂一些。这次,Residence类定义了一个变量属性rooms,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?
}
Because this version of Residence stores an array of Room instances, its numberOfRooms property is implemented as a computed property, not a stored property. The computed numberOfRooms property simply returns the value of the count property from the rooms array.
由于这个版本的Residence保存了一个Room数组实例,因此它的numberOfRooms属性就是一个计算属性,而不是一个存储属性。numberOfRooms属性返回rooms数组的count属性值。
As a shortcut to accessing its rooms array, this version of Residence provides a read-write subscript that provides access to the room at the requested index in the rooms array.
为了快速获取它的rooms数组,这个版本的Residence提供了一个可读写的脚本,此脚本提供了一种获取位于rooms数组的请求的下标的room的方法。
This version of Residence also provides a method called printNumberOfRooms, which simply prints the number of rooms in the residence.
Finally, Residence defines an optional property called address, with a type of Address?. The Address class type for this property is defined below.
这个版本的Residence还提供了一个方法printNumberOfRooms,此方法用于打印residence的rooms的数量。
最后,Residence定义了一个可选属性address,类型为Address?。Address类型在下面定义。
The Room class used for the rooms array is a simple class with one property called name, and an initializer to set that property to a suitable room name:
rooms数组中的Room类只有一个属性name,初始化方法把此属性设上一个合适的房间名:
class Room {
let name: String
init(name: String) { self.name = name }
}
The final class in this model is called Address. This class has three optional properties of type String?. The first two properties, buildingName and buildingNumber, are alternative ways to identify a particular building as part of an address. The third property, street, is used to name the street for that address:
最后一个类是Address。这个类有三个类型为String?的可选属性。前两个属性,buildingName和buildingNumber,用于标记地址中的特定建筑。第三个属性,street,用于地址中的街道名:
class Address {
var buildingName: String?
var buildingNumber: String?
var street: String?
func buildingIdentifier() -> String? {
if let buildingNumber = buildingNumber, let street = street {
return "\(buildingNumber) \(street)"
} else if buildingName != nil {
return buildingName
} else {
return nil
}
}
}
The Address class also provides a method called buildingIdentifier(), which has a return type of String?. This method checks the properties of the address and returns buildingName if it has a value, or buildingNumber concatenated with street if both have values, or nil otherwise.
Address提供了一个方法buildingIdentifier(),此方法的返回类型是String?。此方法检查地址的属性并返回buildingName,如果buildingName有值的话,或者返回buildingNumber串联street后的值,如果这两个属性都有值的话,否则返回nil。
通过可选择链获取属性
Use the classes defined above to create a new Person instance, and try to access its numberOfRooms property as before:
使用上面定义的类来创建一个新的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."
Because john.residence is nil, this optional chaining call fails in the same way as before.
由于john.residence是nil,所以这个可选择链像之前那样失败了。
You can also attempt to set a property’s value through optional chaining:
你还可以通过可选择链试着设置属性的值:
let someAddress = Address()
someAddress.buildingNumber = "29"
someAddress.street = "Acacia Road"
john.residence?.address = someAddress
In this example, the attempt to set the address property of john.residence will fail, because john.residence is currently nil.
在这个例子中,设置john.residence的address属性将会失败,因为john.residence当前为nil。
The assignment is part of the optional chaining, which means none of the code on the right hand side of the = operator is evaluated. In the previous example, it’s not easy to see that someAddress is never evaluated, because accessing a constant doesn’t have any side effects. The listing below does the same assignment, but it uses a function to create the address. The function prints “Function was called” before returning a value, which lets you see whether the right hand side of the = operator was evaluated.
赋值是可选择链的一部分,这意味着=操作符右边的代码没有被计算。在前面的例子中,并不容易看出someAddress从没被计算,因为获取一个常量没有任何副作用。下面的代码实现一样的赋值操作,但是它使用一个函数来创建address。该函数会在返回一个值之前打印“Function was called”,这可以让你了解到=操作符右边的内容有没有被计算:
func createAddress() -> Address {
print("Function was called.")
let someAddress = Address()
someAddress.buildingNumber = "29"
someAddress.street = "Acacia Road"
return someAddress
}
john.residence?.address = createAddress()
You can tell that the createAddress() function isn’t called, because nothing is printed.
可以看到createAddress()函数并没有被调用,因为没有内容被打印。
通过可选择链调用方法
You can use optional chaining to call a method on an optional value, and to check whether that method call is successful. You can do this even if that method does not define a return value.
你可以通过可选择链调用一个可选类型中的方法,并查看该方法调用是否成功。即使该方法没有定义一个返回值。
The printNumberOfRooms() method on the Residence class prints the current value of numberOfRooms. Here’s how the method looks:
Residence类的printNumberOfRooms()方法打印numberOfRooms的当前值:
func printNumberOfRooms() {
print("The number of rooms is \(numberOfRooms)")
}
This method does not specify a return type. However, functions and methods with no return type have an implicit return type of Void, as described in Functions Without Return Values. This means that they return a value of (), or an empty tuple.
此方法没有返回类型。但是,没有返回类型的函数和方法有一个隐式的返回类型Void。这意味着这些方法返回一个()值,或者一个空的tuple。
If you call this method on an optional value with optional chaining, the method’s return type will be Void?, not Void, because return values are always of an optional type when called through optional chaining. This enables you to use an if statement to check whether it was possible to call the printNumberOfRooms() method, even though the method does not itself define a return value. Compare the return value from the printNumberOfRooms call against nil to see if the method call was successful:
如果你通过可选择链在一个可选类型上调用此方法,此方法的返回类型将是Void?,因为通过可选择链调用的方法都是可选类型的。这使得你可以使用一个if语句来判定是否可以调用printNumberOfRooms()方法,即使该方法本身没有定义返回值。把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."
The same is true if you attempt to set a property through optional chaining.
通过可选择链设属性值也是一样。
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."
获取脚本的可选择链
You can use optional chaining to try to retrieve and set a value from a subscript on an optional value, and to check whether that subscript call is successful.
你可以用可选择链在一个可选类型上用脚本来获取和设置值,并查看脚本调用是否成功。
The example below tries to retrieve the name of the first room in the rooms array of the john.residence property using the subscript defined on the Residence class. Because john.residence is currently nil, the subscript call fails:
下面的例子尝试使用脚本来获取john.residence中的rooms数组中的第一个room的名字。由于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."
The optional chaining question mark in this subscript call is placed immediately after john.residence, before the subscript brackets, because john.residence is the optional value on which optional chaining is being attempted.
可选择链的问号要放在脚本调用之后,在脚本的方括号之前,因为 john.residence是可选类型的。
Similarly, you can try to set a new value through a subscript with optional chaining:
你也可以在可选择链上用脚本设置一个新的值:
john.residence?[0] = Room(name: "Bathroom")
This subscript setting attempt also fails, because residence is currently nil.
由于residence当前为nil,所以这个赋值也是失败的。
If you create and assign an actual Residence instance to john.residence, with one or more Room instances in its rooms array, you can use the Residence subscript to access the actual items in the rooms array through optional chaining:
如果你创建一个Residence实例并把它赋给 john.residence,那么你可以在可选择链上用Residence脚本来获取rooms数组中的项:
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."
可选类型的获取脚本
If a subscript returns a value of optional type—such as the key subscript of Swift’s Dictionary type—place a question mark after the subscript’s closing bracket to chain on its optional return value:
如果一个脚本返回一个可选类型—比如swift的字典类型的key脚本——要在脚本的右括号后加上一个问号,来串起它的可选的返回值:
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]
The example above defines a dictionary called testScores, which contains two key-value pairs that map a String key to an array of Int values. The example uses optional chaining to set the first item in the "Dave" array to 91; to increment the first item in the "Bev" array by 1; and to try to set the first item in an array for a key of "Brian". The first two calls succeed, because the testScores dictionary contains keys for "Dave" and "Bev". The third call fails, because the testScores dictionary does not contain a key for "Brian".
上面的例子定义了一个字典testScores,该字典包含两个把字符串映射到int值数组的键值对。该例子使用可选择链把"Dave"数组的第一项设为91,把"Bev"数组的第一项加1,把"Brian"数组的第一项设为72。前两个调用都是成功的因为testScores字典有"Dave" 和 "Bev"这两个键,第三个调用是失败的,因为testScores字典不包含"Brian"键。
多层链接
You can link together multiple levels of optional chaining to drill down to properties, methods, and subscripts deeper within a model.
你可以对可选链进行多层链接以获取到一个模型内更深层的属性,方法和脚本。
To put it another way:
If the type you are trying to retrieve is not optional, it will become optional because of the optional chaining.
If the type you are trying to retrieve is already optional, it will not become more optional because of the chaining.
Therefore:
If you try to retrieve an Int value through optional chaining, an Int? is always returned, no matter how many levels of chaining are used.
Similarly, if you try to retrieve an Int? value through optional chaining, an Int? is always returned, no matter how many levels of chaining are used.
可以这么说:
如果你想获取的类型不是可选的,由于可选链,它将会变成可选的。
如果你想获取的类型已经是可选的,它不会因为可选链而变得更可选(more optional)。(什么叫更可选???)
因此:
如果你想通过可选链获取一个int值,返回的永远是int?,无论使用了多少层的链接。
类似的,如果你想通过可选链获取一个int?值,返回的也是int?,无论使用了多少层的链接。
The example below tries to access the street property of the address property of the residence property of john. There are two levels of optional chaining in use here, to chain through the residence and address properties, both of which are of optional type:
下面的例子尝试获取john的residence属性的address属性的street属性。用了两层可选择链,链接了residence和address属性,两个属性都是可选类型:
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."
The value of john.residence currently contains a valid Residence instance. However, the value of john.residence.address is currently nil. Because of this, the call to john.residence?.address?.street fails.
john.residence当前包含一个有效的Residence实例,但是john.residence.address当前的值为nil。所以, john.residence?.address?.street这个调用失败。
If you set an actual Address instance as the value for john.residence.address, and set an actual value for the address’s street property, you can access the value of the street property through multilevel optional chaining:
如果你给john.residence.address设置一个有效的Address实例,并给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."
通过可选返回类型链接方法。
The previous example shows how to retrieve the value of a property of optional type through optional chaining. You can also use optional chaining to call a method that returns a value of optional type, and to chain on that method’s return value if needed.
上面的例子展示了如何通过可选择链获取一个可选类型的属性的值。你还可以用可选择链调用一个返回可选类型的方法,如果需要的话,还可以链接该方法的返回类型。
The example below calls the Address class’s buildingIdentifier() method through optional chaining. This method returns a value of type String?. As described above, the ultimate return type of this method call after optional chaining is also String?:
下面的例子通过可选择链调用了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."
If you want to perform further optional chaining on this method’s return value, place the optional chaining question mark after the method’s parentheses:
如果你想在此方法等返回类型上继续链接,可以在方法的扩后后加上一个问号:
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"."
网友评论