?? и ?.
Представьте, что вы идёте в гости с коробкой, которая может оказаться пустой, и на всякий случай берёте запасной подарок. Если в коробке что-то есть, вы дарите это, а если пусто — дарите запасной. В любом случае подарок у вас в руках.
Программе часто нужны не две ветки, а одно число: если в коробке ничего нет, подойдёт запасное. С if let на это уходит несколько строк, а оператор ?? справляется в одной:
let apples = Int("7") ?? 0
let pears = Int("seven") ?? 0
print(apples + pears) // 7Давайте разберём программу по строкам:
Int("7")даёт коробку с7, и??достаёт из неё7.Int("seven")даёт пустую коробку, и??берёт запасное значение0.applesиpears— обычные числа типаInt, поэтому их можно сложить: программа печатает7.
Значение по умолчанию: ??
Запись Int("seven") ?? 0 состоит из трёх частей:
- слева — опционал, который открывают;
??— оператор, который смотрит, есть ли в опционале значение;- справа — значение по умолчанию: его берут, когда слева
nil.
Результат ?? — уже не опционал, а обычное значение: у apples тип Int, а не Int?. Поэтому его можно складывать и печатать без предупреждений.
?? смотрит только на nil
Число 0 — не пустота. В коробке с нулём лежит настоящее значение, и ?? его оставляет:
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». Такую запись называют опциональной цепочкой:
var nickname: String? = nil
print(nickname?.count ?? 0) // 0
nickname = "Trinity"
print(nickname?.count ?? 0) // 7
print(type(of: nickname?.count)) // Optional<Int>Давайте разберём, что происходит:
- Пока коробка
nicknameпуста,nickname?.countне читаетcountвовсе и даётnil.?? 0заменяет его нулём. - После
nickname = "Trinity"в коробке есть строка, и?.читает еёcount— это7. type(of:)показывает, чтоnickname?.countимеет типOptional<Int>, то естьInt?.
Ответ цепочки всегда приходит в коробке: если текста в коробке могло не быть, то и числа символов могло не быть. Поэтому после ?. часто ставят ??, чтобы получить обычное значение.
Если после ?. идёт присваивание или вызов, а в коробке nil, Swift их пропускает: ничего не присваивается и не вызывается.
На собеседовании любят спрашивать, какой тип у nickname?.count. Правильный ответ — Int?, а не Int.
Частая ошибка
Число символов у опционала прочитали через обычную точку:
let letters = nickname.count // ✗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?.