Урок 3.2 Опционалы

Урок 3.2 Опционалы

Одной из самых сильных сторон Swift является его способность считывать код и быстро понимать данные. Когда функция может возвращать или не возвращать данные, Swift заставляет вас правильно обрабатывать оба возможных сценария.


Swift использует уникальный синтаксис, называемый необязательным, для обработки такого рода случаев. В этом уроке вы научитесь использовать опции для правильной обработки ситуаций, когда данные могут существовать, а могут и не существовать.


Что Вы Узнаете

  • Как создать переменные или константы, которые могут не иметь значения
  • Как проверить, содержит ли переменная или константа значение
  • Как создать безопасный, чистый код с использованием необязательной привязки
  • Как создавать функции и инициализаторы, которые возвращают необязательный
  • Когда следует использовать неявно развернутые опции

Словарь

Связанные Ресурсы

Руководство по языку программирования Swift: Опционалы

Руководство по языку программирования Swift: Опциональная последовательность

Руководство по языку программирования Swift:  Инициализация

 

Nil

Опционалы полезны в ситуациях, когда значение может присутствовать, а может и не присутствовать. Опциональный параметр представляет две возможности: либо есть значение, и вы можете его использовать, либо его вообще нет.

Представьте, что вы создаете приложение для книжного магазина, в котором перечислены книги, выставленные на продажу. У вас есть модель объекта типа Book, который имеет свойства name и publicationYear.

struct Book {
  var name: String
  var publicationYear: Int
}
 
let firstDickens = Book(name: "A Christmas Carol",​
 publicationYear: 1843)
let secondDickens = Book(name: "David Copperfield",​
 publicationYear: 1849)
let thirdDickens = Book(name: "A Tale of Two Cities",​
 publicationYear: 1859)
 
let books = [firstDickens, secondDickens, thirdDickens]

Пока все идет хорошо. Теперь представьте, что вы создаете экран, на котором отображается список книг, которые были анонсированы, но еще не опубликованы.
Как вы могли бы инициализировать эти книги без даты публикации? Какой год публикации publicationYear вы бы назначили?
 
let unannouncedBook = Book(name: “Rebels and Lions”
publicationYear: 0)
 
Ноль - это неточно, потому что это означало бы, что книге более 2000 лет.

let unannouncedBook = Book(name: “Rebels and Lions”
publicationYear: 2019)
 
Текущий или даже следующий год также не является точным, поскольку он может быть выпущен через два года. Дата запуска неизвестна.
nil означает отсутствие значения или вообще ничего. Поскольку publicationYear еще не опубликован, publicationYear должен быть nil.
 
let unannouncedBook = Book(name: “Rebels and Lions”
publicationYear:nil)
 
Это выглядит лучше, но компилятор выдает ошибку. Все свойства экземпляра должны быть заданы во время инициализации, и вы не можете передать nil параметру publicationYear, поскольку он ожидает значение Int.

Необязательно, решите эту проблему, предоставив оболочку для значения, которое может существовать. Вы можете представить себе optional как поле, которое при открытии будет содержать либо экземпляр ожидаемого типа, либо вообще ничего (nil).

У каждого типа есть соответствующий необязательный тип, который вы объявляете, добавляя ? после исходного имени типа.

В этом случае вам нужно обновить свойство type annotations on publicationYear до Int?, необязательного значения Int.

struct Book {
  var name: String
  var publicationYear: Int?
}
 
let firstDickens = Book(name: "A Christmas Carol",​
   publicationYear: 1843)
let secondDickens = Book(name: "David Copperfield",​
   publicationYear: 1849)
let thirdDickens = Book(name: "A Tale of Two Cities",​
   publicationYear: 1859)
 
let books = [first Dickens, second Dickens, third Dickens]
 
let unannouncedBook = Book(name: “Rebels and Lions”
publicationYear: nil)

 

Указание типа опциональной переменной

Обратите внимание, что вы не можете создать опциональную переменную без указания типа. Подумайте, что произойдет, если вы попытаетесь разрешить Swift определять тип.

В этом примере при выводе типа предполагается, что ваша переменная не опциональная:

 

var server Response Code = 404 // Int, не Int?

 

В этом примере вывод типа не содержит никакой информации для определения типа данных, если данные не равны нулю:

 

var serverResponseCode = nil // Ошибка, тип не указан, если не "nil`

 

По этим причинам в большинстве случаев вам нужно будет использовать аннотацию типа, чтобы указать тип при создании опциональной переменной или константы. Ознакомьтесь со следующими правильными подходами к Int? опционалу:

 

var serverResponseCode: Int? = 404 // Установлено значение 404, но позже может быть равно "nil"

 
var serverResponseCode: Int? = nil // Установлено значение "nil", но позже может быть добавлено значение "Int"

 

Работа со значениями опционала

Как определить, содержит ли значение опционал значение или нет? Как получить доступ к значению? Вы могли бы начать с сравнения значения опционала с nil, используя оператор if. Если значение не равно нулю, вы можете развернуть значение, используя оператор принудительного извлечения, !.

 

if publicationYear != nil {
  let actualYear = publicationYear!
  print(actualYear)
}

 

Если вы пропустили сравнение опционала с nil и принудительно извлекли опционал, который не содержит значения, код выдаст ошибку и завершится сбоем при попытке его запуска.

 

let unwrappedYear = publicationYear! // ошибка выполнения
print(unwrappedNumber)

 

Кажется, хорошей практикой будет сравнивать опционал с nil перед попыткой использовать его значение, но это также кажется избыточным. Как вы помните, безопасность и ясность являются основными целями дизайна Swift, поэтому предоставляется лаконичный синтаксис для безопасного использования значения опционала, если оно существует, и избегания ошибок, если его нет.

Опциональное связывание (optional binding) извлекает опционал и, если он содержит значение, присваивает это значение константе как типу без опционала, что делает его безопасным для работы. Этот подход устраняет необходимость продолжать работу с неопределенностью, работаете ли вы с значением или с nil

Синтаксис опционального связывания выглядит следующим образом:

 

if let constantName = someOptional {
  // имя константы было безопасно развернуто для использования в фигурных скобках
}

 

Если someOptional имеет значение, это значение присваивается constantName и доступно только в пределах фигурных скобок. Посмотрите, как работает опциональное связывание на предыдущем примере о книге:

 

if let unwrappedPublicationYear = book.publicationYear {
  print(”Книга была опубликована в \ (unwrappedPublicationYear)”)
}

 

Так же, как и в других операторах if, вы можете добавить блок else.

 

if let unwrappedPublicationYear = book.publicationYear {
  print(”Книга была опубликована в \(unwrappedPublicationYear)”)
}
else {
  print(”У книги нет официальной даты публикации.”)
}

 

 

Функции и Опционалы

В Swift есть много функций, которые возвращают опциональные значения.

Рассмотрим пример, где у вас есть String со значением "123". Вы можете видеть, что эта строка может быть преобразована в Int.

 

let string = “123”

let possibleNumber = Int(string)

 

Но что если строку нельзя преобразовать?

 

let string = “Cynthia”

let possibleNumber = Int(string)

 

Swift интерпретирует possibleNumber как тип Int?, потому что инициализатор для Int, который принимает строку в качестве параметра, может успешно или неудачно преобразовать строку в Int. Если строка может быть преобразована в Int, possibleNumber будет содержать это значение. Если не может, possibleNumber будет равен nil.

Если вы хотите написать функцию, которая принимает опционал в качестве аргумента, просто укажите тип параметра как опциональный. Рассмотрим эту функцию печати, которая принимает firstName, middleName и lastName, но допускает, что middleName может быть nil, так как не у всех есть среднее имя.

 

func printFullName(firstName: String, middleName: String?,
   lastName: String)

 

То же самое верно для функции, которая возвращает опционал: просто укажите тип возвращаемого значения как опциональный. Например, функция, которая принимает URL-адрес веб-сайта и возвращает текст с этой страницы. Возвращаемый текст является опционалом, потому что URL-адрес может не работать или может не возвращать никакого текста.

 

func textFromURL(url: URL) -> String?

 

Ошибкоустойчивые инициализаторы

 

Любой инициализатор, который может вернуть nil, называется ошибкоустойчивым инициализатором. Ранее в этом уроке вы видели, как инициализатор Int пытался создать целое число из String и возвращал nil, если не мог преобразовать String.

Для большего контроля и безопасности вы можете создать свои собственные ошибкоустойчивые инициализаторы и определить логику возвращения экземпляра или nil.

Рассмотрим следующую структуру для Toddler (малыш):

 

struct Toddler {
  var name: String
  var monthsOld: Int
}

 

В этом примере каждому Toddler должно быть присвоено имя, а также возраст в месяцах. Однако вы можете не захотеть создавать экземпляр малыша, если ребенку меньше 12 месяцев или больше 36 месяцев. Для предоставления этой гибкости вы можете использовать init? для определения ошибкоустойчивого инициализатора. Вопросительный знак (?) указывает Swift, что этот инициализатор может вернуть nil и должен возвращать экземпляр типа Toddler?.

В теле инициализатора вы можете проверить, меньше ли указанный возраст 12 месяцев или больше 36 месяцев. Если любое из этих условий истинно, инициализатор возвращает nil вместо присвоения значения monthsOld:

 

struct Toddler {
  var name: String
  var monthsOld: Int
 
  init?(name: String, monthsOld: Int) {
    if monthsOld < 12 || monthsOld > 36 {
      return nil
    } else {
      self.name = name
      self.monthsOld = monthsOld
    }
  }
}

 

Когда вы используете ошибкоустойчивый инициализатор для создания экземпляров Toddler, всегда будет возвращаться опционал. Чтобы безопасно извлечь значение перед его использованием, вы можете использовать опциональное связывание:

 

let toddler = Toddler(name: “Joanna”, monthsOld: 14)

if let myToddler = toddler {
  print(”Малыша зовут \(myToddler.name) и ему \(myToddler.monthsOld) месяцев”)
} else {
  print(”Возраст, указанный вами для малыша, не находится между 1
 и 3 года
)
}

 

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

Опциональное значение также может иметь опциональные свойства, которые можно представить как коробку внутри коробки. Эти свойства называются вложенными опционалами.

В следующем примере обратите внимание, что у каждого Person (человека) есть age (возраст) и, возможно, residence место жительства. У Residence (места жительства) может быть address (адрес), и не у каждого Address (адреса) есть apartmentNumber (номер квартиры).

 

struct Person {
  var age: Int
  var residence: Residence?
}
 
struct Residence {
  var address: Address?
}
 
struct Address {
  var buildingNumber: String
  var streetName: String
  var apartmentNumber: String?
}

 

Разворачивание вложенных опционалов может потребовать много кода. В следующем примере вы проверяете адрес человека, чтобы узнать, живет ли он в квартире. Чтобы сделать это для данного объекта Person, вам нужно развернуть опциональное значение residence, опциональное значение address и опциональное значение apartmentNumber. Используя синтаксис if-let, вам придется выполнить довольно много условного разворачивания:

 

if let theResidence = person.residence {
  if let theAddress = theResidence.address {
    if let theApartmentNumber = theAddress.apartmentNumber {
      print(”Он/она живет в квартире номер \(theApartmentNumber).”)
    }
  }
}

 

Но есть лучший способ сделать это. Вместо того чтобы присваивать имя каждому опционалу, вы можете условно разворачивать каждое свойство, используя конструкцию, известную как опциональная цепочка. Если у человека есть место жительства, у которого есть адрес, который, в свою очередь, имеет номер квартиры, и если этот номер квартиры можно преобразовать в целое число, тогда вы можете обратиться к этому номеру, используя theApartmentNumber, как показано здесь:

 

if let theApartmentNumber = person.residence?.address?.apartmentNumber {
   print("Он/она живет в квартире номер \(theApartmentNumber).”)
}

 

При связывании вместе опционалов перед каждым опционалом в цепочке появляется ?.

Если значение nil прерывает цепочку в любой точке, оператор if let не выполняется. В результате никакое значение не присваивается константе, и код внутри фигурных скобок не выполняется. Если ни одно из значений не является nil, код внутри фигурных скобок выполняется, и константа получает значение.

 

Неявно извлекаемые опционалы

Объект не может быть инициализирован до тех пор, пока всем его свойствам, не являющимся опциональными, не будут присвоены значения. Но в некоторых случаях, особенно при разработке для iOS, некоторые свойства могут быть равны nil всего на мгновение, пока значение не будет присвоено после инициализации. Например, вы использовали Interface Builder для создания outlet'ов, чтобы иметь возможность обращаться к определенной части интерфейса в коде.

 

class ViewController: UIViewController {
  @IBOutlet var label: UILabel!
}

 

Если бы вы были разработчиком этого класса, вы бы знали, что каждый раз, когда создается и представляется пользователю ViewController, на экране всегда будет метка (label), потому что вы добавили ее в storyboard. Но в разработке для iOS элементы storyboard не соединяются с соответствующими outlet'ами до завершения инициализации. Следовательно, метке (label) должно быть разрешено быть nil на короткое время.

Что если использовать обычный опционал, UILabel?, для типа? Стандартный опционал потребует использования синтаксиса if-let для постоянного извлечения значения, предоставляя механизм безопасности для данных, которые могут не существовать. Но вы знаете, что метка (label) будет иметь значение после того, как storyboard соединит outlet'ы, поэтому разворачивание опционала, который на самом деле не является "опциональным", кажется громоздким.

Чтобы обойти эту проблему, Swift предоставляет синтаксис для неявно извлекаемого опционала, используя восклицательный знак ! вместо вопросительного знака ?. Как следует из названия, этот тип опционала извлекается автоматически, когда он используется в коде. Это позволяет вам использовать метку (label) так, как будто она не является опциональной, при этом позволяя инициализировать ViewController без нее.

Неявно извлекаемые опционалы следует использовать в одном особом случае: когда вам нужно инициализировать объект без присвоения значения, но вы знаете, что присвоите значение объекту до того, как любой другой код попытается получить к нему доступ. Это может показаться удобным — злоупотреблять неявно извлекаемыми опционалами, чтобы избавить себя от необходимости использовать синтаксис if let, но при этом вы лишаете язык важной функции безопасности. Таким образом, многие разработчики на Swift считают, что слишком много ! в коде — это тревожный сигнал. Если вы попытаетесь получить доступ к значению неявно извлекаемого опционала, и это значение будет равно nil, ваша программа завершится с ошибкой.

 

Лабораторная работа

Откройте и завершите упражнения в Lab—Optionals.playground.

 

 


Отрывок из книги
Develop in Swift Fundamentals
Apple Education
https://books.apple.com/ru/book/develop-in-swift-fundamentals/id1581182804

 

Information

Apple, the Apple logo, Apple Books, Apple TV, Apple Watch, Cocoa, Cocoa Touch, Finder, Handoff, HealthKit, iPad, iPad Pro, iPhone, iPod touch, Keynote, Mac, macOS, Numbers, Objective-C, Pages, Photo Booth, Safari, Siri, Spotlight, Swift, tvOS, watchOS, and Xcode are trademarks of Apple Inc., registered in the U.S. and other countries. App Store and iBooks Store are service marks of Apple Inc., registered in the U.S. and other countries. ​
The Bluetooth® word mark and logos are registered trademarks owned by Bluetooth SIG, Inc. and any use of such marks by Apple is under license. ​
IOS is a trademark or registered trademark of Cisco in the U.S. and other countries and is used under license. ​
Other product and company names mentioned herein may be trademarks of their respective companies.