Перейти к содержимому
Теория · Опционалы · урок 6 из 8

?? и ?.

Представьте, что вы идёте в гости с коробкой, которая может оказаться пустой, и на всякий случай берёте запасной подарок. Если в коробке что-то есть, вы дарите это, а если пусто — дарите запасной. В любом случае подарок у вас в руках.

Программе часто нужны не две ветки, а одно число: если в коробке ничего нет, подойдёт запасное. С if let на это уходит несколько строк, а оператор ?? справляется в одной:

SWIFT
let apples = Int("7") ?? 0
let pears = Int("seven") ?? 0
print(apples + pears) // 7

Давайте разберём программу по строкам:

  1. Int("7") даёт коробку с 7, и ?? достаёт из неё 7.
  2. Int("seven") даёт пустую коробку, и ?? берёт запасное значение 0.
  3. apples и pears — обычные числа типа Int, поэтому их можно сложить: программа печатает 7.

Значение по умолчанию: ??

Запись Int("seven") ?? 0 состоит из трёх частей:

  • слева — опционал, который открывают;
  • ?? — оператор, который смотрит, есть ли в опционале значение;
  • справа — значение по умолчанию: его берут, когда слева nil.

Результат ?? — уже не опционал, а обычное значение: у apples тип Int, а не Int?. Поэтому его можно складывать и печатать без предупреждений.

?? смотрит только на nil

Число 0 — не пустота. В коробке с нулём лежит настоящее значение, и ?? его оставляет:

SWIFT
let plums = Int("0") ?? 10
print(plums) // 0

В тексте "0" есть число, поэтому Int("0") даёт коробку с 0, и запасное 10 не нужно. В некоторых языках похожая запись заменяет и ноль, и пустую строку, но ?? в Swift заменяет только nil.

Запасное значение того же типа

Здесь картинка с запасным подарком неточна. В жизни запасным подарком может быть что угодно: цветы вместо книги. А в Swift значение по умолчанию должно быть того же типа, что и содержимое коробки. Строка Int("x") ?? "none" не скомпилируется: binary operator '??' cannot be applied to operands of type 'Int?' and 'String'.

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

У строки можно узнать, сколько в ней символов: "Neo".count — это 3. Число символов читают через точку после строки. Но что, если строка лежит в опционале? Например, это псевдоним пользователя, а пользователь мог его и не задать.

Для этого есть ?.: «если в коробке что-то есть, возьми у этого count». Такую запись называют опциональной цепочкой:

SWIFT
var nickname: String? = nil
print(nickname?.count ?? 0) // 0
nickname = "Trinity"
print(nickname?.count ?? 0) // 7
print(type(of: nickname?.count)) // Optional<Int>

Давайте разберём, что происходит:

  1. Пока коробка nickname пуста, nickname?.count не читает count вовсе и даёт nil. ?? 0 заменяет его нулём.
  2. После nickname = "Trinity" в коробке есть строка, и ?. читает её count — это 7.
  3. type(of:) показывает, что nickname?.count имеет тип Optional<Int>, то есть Int?.

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

Если после ?. идёт присваивание или вызов, а в коробке nil, Swift их пропускает: ничего не присваивается и не вызывается.

На собеседовании любят спрашивать, какой тип у nickname?.count. Правильный ответ — Int?, а не Int.

Частая ошибка

Число символов у опционала прочитали через обычную точку:

SWIFT
let letters = nickname.count // ✗
TEXT
error: value of optional type 'String?' must be unwrapped to refer to member 'count' of wrapped base type 'String'

У String? нет своего count: он есть только у строки внутри коробки, а коробка может быть пуста. Поставьте ? перед точкой: nickname?.count даёт Int?, а nickname?.count ?? 0 — обычный Int.

Главное

  • a ?? b даёт значение из опционала a, а если там nil — значение по умолчанию b того же типа.
  • ?? заменяет только nil: коробка с 0 не пуста, и Int("0") ?? 10 — это 0.
  • nickname?.count читает count, только если в опционале есть строка, а иначе даёт nil.
  • Результат опциональной цепочки — снова опционал: у nickname?.count тип Int?.