Optional как перечисление
Опционал выглядит особым типом. У него свой знак ?, своё слово nil и свои способы достать значение: if let и ??. Но за всем этим стоит обычное перечисление с двумя вариантами.
Вспомните подарочную коробку из темы об опционалах. В ней либо лежит подарок, либо нет ничего, а третьего не бывает. Это меню из двух строк: «коробка с подарком» и «пустая коробка». Сам подарок — как размер кофе на чеке: деталь, которая у каждой коробки своя.
Раз опционал — перечисление, его можно разобрать в switch по вариантам:
let parsed = Int("42")
switch parsed {
case .some(let n):
print("number", n) // number 42
case .none:
print("no number")
}Давайте разберём программу по строкам:
Int("42")даётInt?, то естьOptional<Int>. В строке"42"есть число, поэтому вparsedлежит.some(42).- Шаблон
.some(let n)подходит под коробку с подарком и достаёт42в константуn, как.coffee(let size)доставал размер с чека. - Ветка
case .none:нужна для пустой коробки. Здесь она не выполнится, но без неёswitchне будет исчерпывающим. - Программа печатает
number 42.
Варианты .some и .none
Этот вопрос часто задают на собеседовании: как устроен Optional? Ответ: это перечисление с двумя вариантами:
.some— коробка с подарком; само значение лежит в этом варианте как связанное значение;.none— пустая коробка; это и естьnil.
Варианты можно написать и явно:
let empty: Int? = .none
let five: Int? = .some(5)
let sameFive: Int? = 5
print(empty == nil) // true
print(five == sameFive) // trueДавайте пройдём по программе:
- В
emptyлежит.none, и сравнение сnilпечатаетtrue:nil— короткая запись.none. .some(5)явно кладёт число5в коробкуfive.- В
sameFiveчисло записано без.some. Тогда Swift сам упаковывает его в.some. - Поэтому
five == sameFiveпечатаетtrue: в обеих коробках лежит.some(5).
В обычном коде .some и .none пишут редко: 5 и nil короче и значат то же самое.
Здесь картинка с меню неточна. Меню написано для одного кафе, а перечисление Optional одно на все типы. В Int? подарок — число, в String? — строка, а Dish(rawValue: 2) даёт Dish?, коробку с блюдом.
if let и if case
Запись if let вы уже знаете. Это короткая форма той же проверки варианта. Сравните её с if case:
if case .some(let n) = parsed {
print("number", n) // number 42
}
if let n = parsed {
print("number", n) // number 42
}Что важно знать об этих двух записях:
- обе проверяют, лежит ли в
parsedвариант.some, и достают из него значение вn; if caseработает с любым перечислением, аif let— только с опционалом;- к обеим можно добавить
else: эта ветка выполнится для.none.
Так же устроен и ??: для .some он отдаёт значение из коробки, а для .none — значение по умолчанию.
Частая ошибка
В switch по опционалу разобрали только коробку с подарком:
switch parsed { // ✗
case .some(let n):
print("number", n)
}error: switch must be exhaustiveПрограмма не компилируется. Optional — перечисление, и switch по нему должен разобрать оба варианта, как по любому другому перечислению. Ниже компилятор подсказывает add missing case: '.none', то есть «добавьте ветку для .none». Допишите ветку case .none: для пустой коробки.
Главное
Optional— перечисление с вариантами.someсо значением внутри и.none;nil— это.none.- Значение, которое кладут в опционал, Swift сам упаковывает в
.some. switchпо опционалу разбирает оба варианта:.some(let n)достаёт значение, а.noneловит пустую коробку.if let n = parsed— короткая записьif case .some(let n) = parsed.