Unetway

Swift - Опциональная цепочка

Необязательное сцепление - это процесс запроса и вызова свойств, методов и подписок для необязательного объекта, который может быть в настоящее время 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по умолчанию 1Personэкземпляры имеют необязательное 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()метод.