Опциональные цепочки в Swift
Опциональная цепочка — это процесс запроса и вызова свойств, методов и сабскриптов. Цель запроса или вызова может быть nil.
Опциональная цепочка возвращает два значения:
Если цель имеет значение, вызов выполняется успешно и возвращает это значение
Если цель равна nil, вызов возвращает nil
Несколько запросов или вызовов можно объединить в цепочку; если любой узел равен nil, вся цепочка завершается неудачей.
Опциональная цепочка может заменить принудительное развёртывание
Поместив знак вопроса (?) после опционального значения свойства, метода или сабскрипта, можно определить опциональную цепочку.
| Опциональная цепочка '?' | Восклицательный знак (!) принудительно развёртывает опциональные цепочки методов, свойств и сабскриптов |
| ? ставится после опционального значения для вызова методов, свойств и сабскриптов | ! ставится после опционального значения для вызова методов, свойств и сабскриптов, чтобы принудительно развернуть значение |
| Когда опционал равен nil, выведите более дружелюбное сообщение об ошибке | Когда опционал равен nil, принудительное развёртывание вызывает ошибку выполнения |
Пример опциональной цепочки с использованием восклицательного знака (!)
class Person {
var residence: Residence?
}
class Residence {
var numberOfRooms = 1
}
let john = Person()
//将导致运行时错误
let roomCount = john.residence!.numberOfRooms
Вывод вышеприведённой программы:
fatal error: unexpectedly found nil while unwrapping an Optional value
Попытка использовать восклицательный знак (!) для принудительного развёртывания и получения значения свойства numberOfRooms свойства residence этого человека вызовет ошибку времени выполнения, потому что в данный момент нет значения residence, доступного для развёртывания.
Пример опциональной цепочки с использованием вопросительного знака (?)
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("不能查看房间号")
}
Вывод вышеприведённой программы:
Cannot view room number
Поскольку эта попытка получить numberOfRooms может завершиться неудачей, опциональная цепочка возвращает значение типа Int?, также называемое «опциональным Int». Когда residence пуст (как в примере выше), опциональный Int будет равен nil, поэтому доступ к numberOfRooms невозможен.
Следует отметить, что это также справедливо, когда numberOfRooms является неопциональным Int (Int?). До тех пор пока запрос выполняется через опциональную цепочку, это означает, что numberOfRooms всегда возвращает Int?, а не Int.
Определение классов модели для опциональной цепочки
Вы можете использовать опциональную цепочку для вызова свойств, методов и сабскриптов на нескольких уровнях. Это позволяет использовать сложные модели между ними для доступа к свойствам более низкого уровня и проверки того, можно ли успешно получить доступ к таким свойствам более низкого уровня.
Пример
Определены четыре класса модели, включая многоуровневую опциональную цепочку:
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
}
}
}
Вызов методов через опциональную цепочку
Вы можете использовать опциональную цепочку для вызова метода у опционального значения и проверки успешности вызова метода. Даже если метод не возвращает значение, вы всё равно можете использовать опциональную цепочку, чтобы сделать это.
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("无法输出房间号")
}
Вывод вышеприведённой программы:
Unable to output room number
Используйте инструкцию if, чтобы проверить, можно ли успешно вызвать метод printNumberOfRooms: если вызов метода через опциональную цепочку успешен, неявное возвращаемое значение printNumberOfRooms будет Void; если он не успешен, метод вернёт nil.
Использование опциональной цепочки для вызова сабскриптов
Вы можете использовать опциональную цепочку, чтобы попытаться получить значение из сабскрипта и проверить, успешен ли вызов сабскрипта; однако вы не можете задать сабскрипт через опциональную цепочку.
Пример 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("无法检索到房间")
}
Вывод вышеприведённой программы:
Unable to retrieve the room
В вызове сабскрипта знак вопроса опциональной цепочки размещается сразу после john.residence и перед квадратными скобками сабскрипта, потому что john.residence — это опциональное значение, которое пытается получить опциональная цепочка.
Пример 2
В примере создайте экземпляр Residence для john.residence, и в его массиве rooms есть один или несколько экземпляров Room. Затем вы можете использовать опциональную цепочку, чтобы получить экземпляры в массиве rooms через сабскрипт 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("无法检索到地址。 ")
}
Вывод вышеприведённой программы:
John 所在的街道是 Laurel Street.
Доступ к сабскриптам через вызовы опциональной цепочки
Через вызовы опциональной цепочки мы можем использовать сабскрипты для чтения или записи опционального значения и определить, успешен ли вызов сабскрипта.
Пример
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("无法检索到房间")
}
Вывод вышеприведённой программы:
The first room is named living room
Доступ к сабскриптам опционального типа
Если сабскрипт возвращает значение опционального типа, например сабскрипт по ключу у Dictionary в Swift, вы можете поставить знак вопроса после закрывающей скобки сабскрипта, чтобы образовать цепочку к опциональному возвращаемому значению сабскрипта:
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]
В приведённом выше примере определён массив testScores, содержащий две пары ключ-значение, которые сопоставляют ключи типа String с массивом целых чисел.
В этом примере используются вызовы опциональной цепочки, чтобы установить первый элемент массива "Dave" равным 91, увеличить первый элемент массива "Bev" на 1, а затем попытаться установить первый элемент массива "Brian" равным 72.
Первые два вызова успешны, потому что эти два ключа существуют. Но ключа "Brian" нет в словаре, поэтому третий вызов завершается неудачей.
Связывание нескольких уровней цепочки
Вы можете связать несколько уровней опциональной цепочки вместе, чтобы углубиться в свойства, методы и индексы дальше в модели. Однако многоуровневая опциональная цепочка не может добавить больше уровней, чем уже возвращённое опциональное значение.
Если вы пытаетесь получить значение Int через опциональную цепочку, независимо от того, сколько уровней цепочки вы используете, возвращаемое значение всегда Int?. Аналогично, если вы пытаетесь получить значение Int? через опциональную цепочку, независимо от того, сколько уровней цепочки вы используете, возвращаемое значение всегда Int?.
Пример 1
В следующем примере делается попытка получить свойство street объекта address в свойстве residence объекта john. Здесь используются два уровня опциональной цепочки для связывания свойств residence и address, оба из которых являются опциональными типами:
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("不能检索地址")
}
Вывод вышеприведённой программы:
Unable to retrieve address
Пример 2
Если вы зададите экземпляр Address в качестве значения john.residence.address и зададите фактическое значение для свойства street объекта address, вы сможете получить это значение свойства через многоуровневую опциональную цепочку.
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("无法检索房间")
}
Вывод приведенного выше примера:
The first room is the living room
Связывание функций, возвращающих опциональные значения
Мы также можем использовать опциональную цепочку для вызова методов, возвращающих опциональные значения, и можем продолжить цепочку на этом опциональном значении.
Пример
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("未指定房间号")
}
Вывод вышеприведённой программы:
No room number specifiedДругие расширения