Цепочки опциональных значений
Доступ к членам опционального значения без его разворачивания.
Цепочка опциональных значений — это процесс запроса и вызова свойств, методов и индексов для опционального значения, которое может в данный момент быть nil. Если опциональное значение содержит значение, вызов свойства, метода или индекса завершается успешно; если опциональное значение является nil, вызов свойства, метода или индекса возвращает nil. Несколько запросов могут быть объединены в цепочку, и вся цепочка завершается успешно, если какой-либо её элемент является nil.
Примечание: Цепочки опциональных значений в Swift похожи на отправку сообщений
nilв Objective-C, но работают для любого типа и позволяют проверять успешность или неудачу.
Цепочки опциональных значений как альтернатива принудительному разворачиванию
Вы указываете цепочку опциональных значений, помещая знак вопроса (?) после опционального значения, для которого вы хотите вызвать свойство, метод или индекс, если опциональное значение не является nil. Это очень похоже на размещение восклицательного знака (!) после опционального значения, чтобы принудительно развернуть его значение. Главное различие заключается в том, что цепочка опциональных значений завершается успешно, когда опциональное значение является nil, в то время как принудительное разворачивание вызывает ошибку во время выполнения, когда опциональное значение является nil.
Для отражения того, что цепочка опциональных значений может быть применена к значению nil, результат вызова цепочки опциональных значений всегда является опциональным значением, даже если свойство, метод или индекс, которые вы запрашиваете, возвращают значение, не являющееся опциональным. Вы можете использовать это возвращаемое опциональное значение для проверки, был ли успешен вызов цепочки опциональных значений (возвращённое опциональное значение содержит значение), или он не был успешен из-за значения nil в цепочке (возвращаемое опциональное значение является nil).
В частности, результат вызова цепочки опциональных значений имеет тот же тип, что и ожидаемое возвращаемое значение, но заключён в опциональное значение. Свойство, которое обычно возвращает Int, вернёт Int? при обращении через цепочку опциональных значений.
Следующие несколько фрагментов кода демонстрируют, чем цепочка опциональных значений отличается от принудительного разворачивания и позволяет проверять успешность.
Сначала определяются два класса, называемые Person и Residence:
class Person {
var residence: Residence?
}
class Residence {
var numberOfRooms = 1
} Экземпляры класса Residence имеют одно свойство Int, называемое numberOfRooms, со значением по умолчанию 1. Экземпляры класса Person имеют опциональное свойство residence типа Residence?.
Если вы создадите новый экземпляр класса Person, его свойство residence будет инициализировано значением по умолчанию nil, благодаря тому, что оно является опциональным. В коде ниже john имеет свойство residence со значением nil:
let john = Person()
Если вы попытаетесь получить доступ к свойству numberOfRooms этого объекта residence, поставив восклицательный знак после residence, чтобы принудительно развернуть его значение, вы получите ошибку во время выполнения, потому что нет значения residence для разворачивания:
let roomCount = john.residence!.numberOfRooms // this triggers a runtime error
Вышеприведённый код выполняется успешно, когда у john.residence есть ненулевое значение и устанавливает roomCount в значение Int, содержащее соответствующее количество комнат. Однако этот код всегда вызывает ошибку во время выполнения, когда residence является nil, как показано выше.
Цепочка опциональных значений предоставляет альтернативный способ доступа к значению 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." Это сообщает Swift «присоединиться» к опциональному свойству residence и извлечь значение numberOfRooms, если residence существует.
Поскольку попытка доступа к numberOfRooms может завершиться неудачей, попытка с использованием цепочки опциональных значений возвращает значение типа Int? или «опциональное Int». Когда residence является nil, как в приведённом примере, это опциональное значение Int также будет nil, чтобы отразить тот факт, что доступ к numberOfRooms не удался. К опциональному значению Int осуществляется доступ с помощью привязки к опциональному значению для разворачивания целого числа и присвоения не опционального значения константе roomCount.
Обратите внимание, что это справедливо, даже если numberOfRooms является не опциональным Int. Тот факт, что оно запрашивается через цепочку опциональных значений, означает, что вызов numberOfRooms всегда вернёт Int? вместо Int.
Вы можете присвоить экземпляр класса Residence переменной john.residence, чтобы у неё больше не было значения nil:
john.residence = Residence()
Теперь john.residence содержит фактический экземпляр класса Residence, а не nil. Если вы попытаетесь получить доступ к numberOfRooms с помощью той же цепочки опциональных значений, что и раньше, она теперь вернёт опциональное значение Int?, содержащее значение по умолчанию numberOfRooms, равное 1:
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 с соответствующими свойствами, методами и индексами.
Класс Person определён так же, как и раньше:
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?
} Поскольку эта версия класса Residence хранит массив экземпляров класса Room, его свойство numberOfRooms реализовано как вычисляемое свойство, а не как хранимое свойство. Вычисляемое свойство numberOfRooms просто возвращает значение свойства count из массива rooms.
В качестве сокращения для доступа к массиву rooms эта версия класса Residence предоставляет индексный доступ к комнате по запрошенному индексу в массиве rooms.
Эта версия класса Residence также предоставляет метод, называемый printNumberOfRooms, который просто выводит количество комнат в доме.
Наконец, класс Residence определяет опциональное свойство, называемое address, с типом Address?. Тип класса Address для этого свойства определён ниже.
Класс Room, используемый для массива rooms, — это простой класс с одним свойством, называемым name, и инициализатором для задания этому свойству подходящего названия комнаты:
class Room {
let name: String
init(name: String) { self.name = name }
} Последний класс в этой модели называется 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
}
}
} Класс Address также предоставляет метод, называемый buildingIdentifier(), с возвращаемым типом String?. Этот метод проверяет свойства адреса и возвращает buildingName, если у него есть значение, или buildingNumber, конкатенированное с street, если у обоих есть значения, или nil в противном случае.
Доступ к свойствам через цепочки опциональных значений
Как показано в <doc:OptionalChaining#Optional-Chaining-as-an-Alternative-to-Forced-Unwrapping>, вы можете использовать цепочки опциональных значений для доступа к свойству опционального значения и для проверки успешности доступа к этому свойству.
Используйте классы, определённые выше, для создания нового экземпляра класса 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
В этом примере попытка установить свойство address объекта john.residence завершится неудачей, поскольку у john.residence в настоящее время отсутствует значение nil.
Присваивание является частью цепочки опциональных значений, поэтому ни один из фрагментов кода справа от оператора = не оценивается. В предыдущем примере не так просто заметить, что someAddress никогда не оценивается, поскольку обращение к константе не имеет побочных эффектов. Ниже представлен тот же фрагмент присваивания, но он использует функцию для создания адреса. Функция выводит «Функция была вызвана» перед возвращением значения, что позволяет вам увидеть, был ли оценён фрагмент кода справа от оператора =.
func createAddress() -> Address {
print("Function was called.")
let someAddress = Address()
someAddress.buildingNumber = "29"
someAddress.street = "Acacia Road"
return someAddress
}
john.residence?.address = createAddress() Вы можете заметить, что функция createAddress() не вызывается, потому что ничего не выводится.
Вызов методов через цепочки опциональных значений
Вы можете использовать цепочку опциональных значений для вызова метода опционального значения и проверки успешности вызова этого метода. Вы можете сделать это, даже если метод не определяет возвращаемого значения.
Метод printNumberOfRooms() класса Residence выводит текущее значение numberOfRooms. Вот как выглядит этот метод:
func printNumberOfRooms() {
print("The number of rooms is \(numberOfRooms)")
} Этот метод не указывает возвращаемый тип. Тем не менее, функции и методы без возвращаемого типа имеют неявный возвращаемый тип Void, как описано в <doc:Functions#Functions-Without-Return-Values>. Это означает, что они возвращают значение () или пустой кортеж.
Если вы вызываете этот метод на необязательном значении с помощью оператора необязательной цепочки, тип возвращаемого значения метода будет Void?, а не 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." То же самое относится к попытке установить свойство через необязательную цепочку. Пример выше в <doc:OptionalChaining#Accessing-Properties-Through-Optional-Chaining> пытается установить значение address для john.residence, даже если свойство 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." Доступ к индексам через необязательную цепочку
Вы можете использовать необязательную цепочку для попытки получения и установки значения по индексу для необязательного значения и проверки того, был ли вызов индекса успешным.
Примечание: при доступе к индексу необязательного значения через необязательную цепочку знак вопроса ? следует перед скобками индекса, а не после. Знак вопроса необязательной цепочки всегда следует непосредственно за необязательной частью выражения.
В приведенном ниже примере пытаются получить имя первой комнаты в массиве rooms свойства john.residence, используя индекс, определенный в классе Residence. Поскольку 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 свойству john.residence, содержащий один или несколько экземпляров Room в массиве rooms, вы можете использовать индекс 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." Доступ к индексам необязательного типа
Если индекс возвращает значение необязательного типа — например, индекс ключа типа Dictionary в Swift — поместите знак вопроса ? после закрывающей скобки индекса, чтобы продолжить цепочку на необязательное возвращаемое значение:
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 с массивом значений Int. В примере используется необязательная цепочка для установки первого элемента в массиве "Dave" в значение 91; для увеличения первого элемента в массиве "Bev" на 1; и для попытки установить первый элемент массива для ключа "Brian". Первые два вызова завершаются успешно, так как словарь testScores содержит ключи для "Dave" и "Bev". Третий вызов завершается неудачно, так как словарь testScores не содержит ключа для "Brian".
Связывание нескольких уровней цепочки
Вы можете связать несколько уровней необязательной цепочки, чтобы получить доступ к свойствам, методам и индексам, расположенным глубже в модели. Однако несколько уровней необязательной цепочки не добавляют больше уровней необязательности к возвращаемому значению.
Другими словами:
- Если тип, который вы пытаетесь получить, не является необязательным, он становится необязательным из-за оператора необязательной цепочки.
- Если тип, который вы пытаетесь получить, уже является необязательным, он не становится более необязательным из-за цепочки.
Следовательно:
- Если вы пытаетесь получить значение
Intчерез необязательную цепочку, всегда возвращается значение типаInt?, независимо от количества уровней цепочки. - Аналогично, если вы пытаетесь получить значение
Int?через необязательную цепочку, всегда возвращается значение типаInt?, независимо от количества уровней цепочки.
В примере ниже пытаются получить доступ к свойству street свойства address свойства residence свойства john. Здесь используются два уровня необязательной цепочки для связывания через свойства 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." Значение john.residence в настоящее время содержит допустимый экземпляр Residence. Однако значение john.residence.address в настоящее время является nil. Из-за этого вызов john.residence?.address?.street завершается неудачно.
Обратите внимание, что в приведенном выше примере вы пытаетесь получить значение свойства street. Тип этого свойства — String?. Возвращаемое значение john.residence?.address?.street, следовательно, также является String?, даже несмотря на то, что применены два уровня необязательной цепочки в дополнение к базовому необязательному типу свойства.
Если вы зададите фактический экземпляр 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." В этом примере попытка установить свойство address для john.residence завершится успешно, так как значение john.residence в настоящее время содержит допустимый экземпляр Residence.
Связывание с методами, возвращающими необязательные значения
Предыдущий пример демонстрирует, как получить значение свойства необязательного типа через необязательную цепочку. Вы также можете использовать необязательную цепочку для вызова метода, возвращающего значение необязательного типа, и при необходимости продолжить цепочку на возвращаемое значение этого метода.
В примере ниже вызывается метод buildingIdentifier() класса Address через необязательную цепочку. Этот метод возвращает значение типа 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 let beginsWithThe =
john.residence?.address?.buildingIdentifier()?.hasPrefix("The") {
if beginsWithThe {
print("John's building identifier begins with \"The\".")
} else {
print("John's building identifier doesn't begin with \"The\".")
}
}
// Prints "John's building identifier begins with "The"." Примечание: в примере выше знак вопроса необязательной цепочки ставится после скобок, так как необязательное значение, на котором производится связывание по цепочке, является возвращаемым значением метода
buildingIdentifier(), а не сам методbuildingIdentifier().
This source file is part of the Swift.org open source project
Copyright © 2014 - 2025 Apple Inc. and the Swift project authors
Licensed under Apache License v2.0 with Runtime Library Exception