Необязательное сцепление - это процесс запроса и вызова свойств, методов и подписок для необязательного объекта, который может быть в настоящее время 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
имеет не- nil
значения и будет установлен 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
иным образом.
Доступ к свойствам через необязательную цепочку
Вы можете использовать необязательное связывание, чтобы получить доступ к свойству с необязательным значением и проверить успешность доступа к этому свойству.
Используйте классы, определенные выше, чтобы создать новый 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
. Это означает, что они возвращают значение ()
или пустой кортеж.
Если вы вызываете этот метод для необязательного значения с необязательной цепочкой, тип возвращаемого метода будет 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."
То же самое верно, если вы пытаетесь установить свойство с помощью необязательной цепочки. В приведенном выше примере в разделе «Доступ к свойствам через необязательную цепочку» предпринимается попытка установить 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."
Доступ к подпискам необязательного типа
Если нижний индекс возвращает значение необязательного типа, например ключевой индекс типа 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
ключ с массивом Int
значений. В примере используется необязательное сцепление для установки первого элемента в "Dave"
массиве 91
; увеличить первый элемент в "Bev"
массиве на 1
; и попытаться задать первый элемент в массиве для ключа "Brian"
. Первые два вызова успешны, потому что testScores
словарь содержит ключи для "Dave"
и "Bev"
. Третий вызов не удался, потому что testScores
словарь не содержит ключ для "Brian"
.
Связывание нескольких уровней цепочки
Вы можете связать воедино несколько уровней необязательной цепочки, чтобы глубже изучить свойства, методы и подписки внутри модели. Тем не менее, несколько уровней необязательного связывания не добавляют больше уровней необязательности к возвращаемому значению.
Другими словами:
- Если тип, который вы пытаетесь получить, не является необязательным, он станет необязательным из-за необязательной цепочки.
- Если тип, который вы пытаетесь получить, уже является необязательным, он не станет более необязательным из-за цепочки.
Следовательно:
- Если вы пытаетесь получить
Int
значение с помощью необязательной цепочки,Int?
всегда возвращается an , независимо от того, сколько уровней цепочки используется. - Точно так же, если вы пытаетесь получить
Int?
значение с помощью необязательной цепочки,Int?
всегда возвращается an , независимо от того, сколько уровней цепочки используется.
В приведенном ниже примере пытается получить доступ к 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
экземпляр.
Цепочка для методов с необязательными возвращаемыми значениями
В предыдущем примере показано, как получить значение свойства необязательного типа с помощью необязательного сцепления. Вы также можете использовать необязательную цепочку для вызова метода, который возвращает значение необязательного типа, и для цепочки возвращаемого значения этого метода, если это необходимо.
В приведенном ниже примере метод 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 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()
это возвращаемое значение метода, а не самbuildingIdentifier()
метод.
0 комментариев