Swift Encadenamiento Opcional
El encadenamiento opcional (Optional Chaining) es un proceso que permite solicitar y llamar propiedades, métodos y subíndices, cuyo destino puede ser nil.
El encadenamiento opcional devuelve dos valores:
Si el destino tiene un valor, la llamada tiene éxito y devuelve ese valor
Si el destino es nil, la llamada devolverá nil
Múltiples solicitudes o llamadas pueden encadenarse formando una cadena; si cualquier nodo es nil, toda la cadena falla.
El encadenamiento opcional puede reemplazar al desempaquetado forzado
Colocando un signo de interrogación (?) después del valor opcional de una propiedad, método o subíndice, se puede definir un encadenamiento opcional.
| Encadenamiento opcional '?' | El signo de exclamación (!) fuerza el desarrollo (unwrap) de métodos, propiedades y subíndices en el encadenamiento opcional |
| ? se coloca después del valor opcional para llamar a métodos, propiedades y subíndices | ! se coloca después del valor opcional para llamar a métodos, propiedades y subíndices y forzar el desarrollo del valor |
| Cuando el opcional es nil, se muestra un mensaje de error más amigable | Cuando el opcional es nil, el desarrollo forzado provoca un error |
Ejemplo de encadenamiento opcional con signo de exclamación (!)
class Person {
var residence: Residence?
}
class Residence {
var numberOfRooms = 1
}
let john = Person()
//将导致运行时错误
let roomCount = john.residence!.numberOfRooms
El resultado de ejecutar el programa anterior es:
fatal error: unexpectedly found nil while unwrapping an Optional value
Si se desea usar el signo de exclamación (!) para forzar la resolución y obtener el valor de la propiedad numberOfRooms de la propiedad residence de esta persona, se producirá un error en tiempo de ejecución, porque en este momento no hay ningún valor residence que se pueda resolver.
Ejemplo de encadenamiento opcional con signo de interrogación (?)
class Person {
var residence: Residence?
}
class Residence {
var numberOfRooms = 1
}
let john = Person()
// 链接可选residence?属性,如果residence存在则取回numberOfRooms的值
if let roomCount = john.residence?.numberOfRooms {
print("John 的房间号为 \(roomCount)。")
} else {
print("不能查看房间号")
}
El resultado de ejecutar el programa anterior es:
No se puede ver el número de habitación
Debido a que esta operación de obtener numberOfRooms puede fallar, el encadenamiento opcional devuelve un valor de tipo Int?, también llamado "Int opcional". Cuando residence está vacío (en el ejemplo anterior), el Int opcional será nil, por lo que no se podrá acceder a numberOfRooms.
Cabe señalar que esto también se cumple incluso cuando numberOfRooms es un Int no opcional (Int?). Siempre que se haga una solicitud a través del encadenamiento opcional, numberOfRooms devolverá al final un Int? en lugar de un Int.
Definir clases de modelo para el encadenamiento opcional
Puedes usar el encadenamiento opcional para llamar a propiedades, métodos y subíndices en múltiples niveles. Esto te permite aprovechar los modelos complejos entre ellos para acceder a propiedades de niveles más profundos y comprobar si se puede acceder correctamente a dichas propiedades subyacentes.
Ejemplo
Se definen cuatro clases de modelo que incluyen encadenamiento opcional de múltiples niveles:
class Person {
var residence: Residence?
}
// 定义了一个变量 rooms,它被初始化为一个Room[]类型的空数组
class Residence {
var rooms = [Room]()
var numberOfRooms: Int {
return rooms.count
}
subscript(i: Int) -> Room {
return rooms[i]
}
func printNumberOfRooms() {
print("房间号为 \(numberOfRooms)")
}
var address: Address?
}
// Room 定义一个name属性和一个设定room名的初始化器
class Room {
let name: String
init(name: String) { self.name = name }
}
// 模型中的最终类叫做Address
class Address {
var buildingName: String?
var buildingNumber: String?
var street: String?
func buildingIdentifier() -> String? {
if (buildingName != nil) {
return buildingName
} else if (buildingNumber != nil) {
return buildingNumber
} else {
return nil
}
}
}
Llamar a métodos mediante el encadenamiento opcional
Puedes usar el encadenamiento opcional para llamar a métodos de valores opcionales y comprobar si la llamada al método tiene éxito. Incluso si el método no devuelve un valor, puedes usar el encadenamiento opcional para lograrlo.
class Person {
var residence: Residence?
}
// 定义了一个变量 rooms,它被初始化为一个Room[]类型的空数组
class Residence {
var rooms = [Room]()
var numberOfRooms: Int {
return rooms.count
}
subscript(i: Int) -> Room {
return rooms[i]
}
func printNumberOfRooms() {
print("房间号为 \(numberOfRooms)")
}
var address: Address?
}
// Room 定义一个name属性和一个设定room名的初始化器
class Room {
let name: String
init(name: String) { self.name = name }
}
// 模型中的最终类叫做Address
class Address {
var buildingName: String?
var buildingNumber: String?
var street: String?
func buildingIdentifier() -> String? {
if (buildingName != nil) {
return buildingName
} else if (buildingNumber != nil) {
return buildingNumber
} else {
return nil
}
}
}
let john = Person()
if ((john.residence?.printNumberOfRooms()) != nil) {
print("输出房间号")
} else {
print("无法输出房间号")
}
El resultado de ejecutar el programa anterior es:
No se puede mostrar el número de habitación
Usa una sentencia if para comprobar si se puede llamar correctamente al método printNumberOfRooms: si la llamada al método a través del encadenamiento opcional tiene éxito, el valor de retorno implícito de printNumberOfRooms será Void; si no tiene éxito, devolverá nil.
Usar el encadenamiento opcional para llamar a subíndices
Puedes usar el encadenamiento opcional para intentar obtener un valor de un subíndice y comprobar si la llamada al subíndice tiene éxito; sin embargo, no puedes usar el encadenamiento opcional para establecer un subíndice.
Ejemplo 1
class Person {
var residence: Residence?
}
// 定义了一个变量 rooms,它被初始化为一个Room[]类型的空数组
class Residence {
var rooms = [Room]()
var numberOfRooms: Int {
return rooms.count
}
subscript(i: Int) -> Room {
return rooms[i]
}
func printNumberOfRooms() {
print("房间号为 \(numberOfRooms)")
}
var address: Address?
}
// Room 定义一个name属性和一个设定room名的初始化器
class Room {
let name: String
init(name: String) { self.name = name }
}
// 模型中的最终类叫做Address
class Address {
var buildingName: String?
var buildingNumber: String?
var street: String?
func buildingIdentifier() -> String? {
if (buildingName != nil) {
return buildingName
} else if (buildingNumber != nil) {
return buildingNumber
} else {
return nil
}
}
}
let john = Person()
if let firstRoomName = john.residence?[0].name {
print("第一个房间名 \(firstRoomName).")
} else {
print("无法检索到房间")
}
El resultado de ejecutar el programa anterior es:
No se puede recuperar la habitación
En la llamada al subíndice, el signo de interrogación del encadenamiento opcional va directamente después de john.residence y antes de los paréntesis del subíndice, porque john.residence es el valor opcional que el encadenamiento opcional intenta obtener.
Ejemplo 2
En el ejemplo se crea una instancia de Residence para john.residence, y hay una o más instancias de Room en su array rooms. Entonces puedes usar el encadenamiento opcional para obtener las instancias del array rooms mediante el subíndice de Residence:
class Person {
var residence: Residence?
}
// 定义了一个变量 rooms,它被初始化为一个Room[]类型的空数组
class Residence {
var rooms = [Room]()
var numberOfRooms: Int {
return rooms.count
}
subscript(i: Int) -> Room {
return rooms[i]
}
func printNumberOfRooms() {
print("房间号为 \(numberOfRooms)")
}
var address: Address?
}
// Room 定义一个name属性和一个设定room名的初始化器
class Room {
let name: String
init(name: String) { self.name = name }
}
// 模型中的最终类叫做Address
class Address {
var buildingName: String?
var buildingNumber: String?
var street: String?
func buildingIdentifier() -> String? {
if (buildingName != nil) {
return buildingName
} else if (buildingNumber != nil) {
return buildingNumber
} else {
return nil
}
}
}
let john = Person()
let johnsHouse = Residence()
johnsHouse.rooms.append(Room(name: "客厅"))
johnsHouse.rooms.append(Room(name: "厨房"))
john.residence = johnsHouse
let johnsAddress = Address()
johnsAddress.buildingName = "The Larches"
johnsAddress.street = "Laurel Street"
john.residence!.address = johnsAddress
if let johnsStreet = john.residence?.address?.street {
print("John 所在的街道是 \(johnsStreet)。")
} else {
print("无法检索到地址。 ")
}
El resultado de ejecutar el programa anterior es:
John 所在的街道是 Laurel Street.
Acceder a subíndices mediante llamadas de encadenamiento opcional
Mediante las llamadas de encadenamiento opcional, podemos usar subíndices para leer o escribir valores opcionales y determinar si la llamada al subíndice tiene éxito.
Ejemplo
class Person {
var residence: Residence?
}
// 定义了一个变量 rooms,它被初始化为一个Room[]类型的空数组
class Residence {
var rooms = [Room]()
var numberOfRooms: Int {
return rooms.count
}
subscript(i: Int) -> Room {
return rooms[i]
}
func printNumberOfRooms() {
print("房间号为 \(numberOfRooms)")
}
var address: Address?
}
// Room 定义一个name属性和一个设定room名的初始化器
class Room {
let name: String
init(name: String) { self.name = name }
}
// 模型中的最终类叫做Address
class Address {
var buildingName: String?
var buildingNumber: String?
var street: String?
func buildingIdentifier() -> String? {
if (buildingName != nil) {
return buildingName
} else if (buildingNumber != nil) {
return buildingNumber
} else {
return nil
}
}
}
let john = Person()
let johnsHouse = Residence()
johnsHouse.rooms.append(Room(name: "客厅"))
johnsHouse.rooms.append(Room(name: "厨房"))
john.residence = johnsHouse
if let firstRoomName = john.residence?[0].name {
print("第一个房间名为\(firstRoomName)")
} else {
print("无法检索到房间")
}
El resultado de ejecutar el programa anterior es:
La primera habitación se llama sala de estar
Acceder a subíndices de tipos opcionales
Si el subíndice devuelve un valor de tipo opcional, como el subíndice de clave de Dictionary en Swift. Puedes colocar un signo de interrogación después del paréntesis de cierre del subíndice para encadenar el valor de retorno opcional del subíndice:
var testScores = ["Dave": [86, 82, 84], "Bev": [79, 94, 81]] testScores["Dave"]?[0] = 91 testScores["Bev"]?[0]++ testScores["Brian"]?[0] = 72 // the "Dave" array is now [91, 82, 84] and the "Bev" array is now [80, 94, 81]
En el ejemplo anterior se define un diccionario testScores que contiene dos pares clave-valor, que mapean claves de tipo String a un array de enteros.
Este ejemplo usa llamadas de encadenamiento opcional para establecer el primer elemento del array de 'Dave' en 91, incrementar en 1 el primer elemento del array de 'Bev' y, a continuación, intentar establecer el primer elemento del array de 'Brian' en 72.
Las dos primeras llamadas tienen éxito porque esas dos claves existen. Sin embargo, la clave 'Brian' no existe en el diccionario, por lo que la tercera llamada falla.
Concatenar múltiples niveles de enlaces
Puedes concatenar varios niveles de encadenamiento opcional para acceder a propiedades, métodos y subíndices más profundos dentro del modelo. Sin embargo, el encadenamiento opcional de varios niveles no puede añadir más niveles de los que ya devuelve el valor opcional.
Si intentas obtener un valor Int mediante el encadenamiento opcional, sin importar cuántos niveles de enlaces utilices, siempre devolverá Int?. De manera similar, si intentas obtener un valor Int? mediante el encadenamiento opcional, sin importar cuántos niveles de enlaces utilices, siempre devolverá Int?.
Ejemplo 1
El siguiente ejemplo intenta obtener la propiedad street de la propiedad address dentro de la propiedad residence de john. Aquí se usan dos niveles de encadenamiento opcional para relacionar las propiedades residence y address, ambas de tipo opcional:
class Person {
var residence: Residence?
}
// 定义了一个变量 rooms,它被初始化为一个Room[]类型的空数组
class Residence {
var rooms = [Room]()
var numberOfRooms: Int {
return rooms.count
}
subscript(i: Int) -> Room {
return rooms[i]
}
func printNumberOfRooms() {
print("房间号为 \(numberOfRooms)")
}
var address: Address?
}
// Room 定义一个name属性和一个设定room名的初始化器
class Room {
let name: String
init(name: String) { self.name = name }
}
// 模型中的最终类叫做Address
class Address {
var buildingName: String?
var buildingNumber: String?
var street: String?
func buildingIdentifier() -> String? {
if (buildingName != nil) {
return buildingName
} else if (buildingNumber != nil) {
return buildingNumber
} else {
return nil
}
}
}
let john = Person()
if let johnsStreet = john.residence?.address?.street {
print("John 的地址为 \(johnsStreet).")
} else {
print("不能检索地址")
}
El resultado de ejecutar el programa anterior es:
No se puede recuperar la dirección
Ejemplo 2
Si estableces una instancia de Address como valor de john.residence.address y asignas un valor real a la propiedad street de address, puedes obtener ese valor de propiedad mediante el encadenamiento opcional de varios niveles.
class Person {
var residence: Residence?
}
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("房间号为 \(numberOfRooms)")
}
var address: Address?
}
class Room {
let name: String
init(name: String) { self.name = name }
}
class Address {
var buildingName: String?
var buildingNumber: String?
var street: String?
func buildingIdentifier() -> String? {
if (buildingName != nil) {
return buildingName
} else if (buildingNumber != nil) {
return buildingNumber
} else {
return nil
}
}
}
let john = Person()
john.residence?[0] = Room(name: "浴室")
let johnsHouse = Residence()
johnsHouse.rooms.append(Room(name: "客厅"))
johnsHouse.rooms.append(Room(name: "厨房"))
john.residence = johnsHouse
if let firstRoomName = john.residence?[0].name {
print("第一个房间是\(firstRoomName)")
} else {
print("无法检索房间")
}
El resultado del ejemplo anterior es:
La primera habitación es la sala de estar
Encadenar funciones que devuelven valores opcionales
También podemos usar el encadenamiento opcional para llamar a métodos que devuelven valores opcionales, y podemos continuar encadenando valores opcionales.
Ejemplo
class Person {
var residence: Residence?
}
// 定义了一个变量 rooms,它被初始化为一个Room[]类型的空数组
class Residence {
var rooms = [Room]()
var numberOfRooms: Int {
return rooms.count
}
subscript(i: Int) -> Room {
return rooms[i]
}
func printNumberOfRooms() {
print("房间号为 \(numberOfRooms)")
}
var address: Address?
}
// Room 定义一个name属性和一个设定room名的初始化器
class Room {
let name: String
init(name: String) { self.name = name }
}
// 模型中的最终类叫做Address
class Address {
var buildingName: String?
var buildingNumber: String?
var street: String?
func buildingIdentifier() -> String? {
if (buildingName != nil) {
return buildingName
} else if (buildingNumber != nil) {
return buildingNumber
} else {
return nil
}
}
}
let john = Person()
if john.residence?.printNumberOfRooms() != nil {
print("指定了房间号)")
} else {
print("未指定房间号")
}
El resultado de ejecutar el programa anterior es:
No se ha especificado el número de habitaciónOtras extensiones