Опционалы и безопасная распаковка
Опционал — не «переменная, которая может быть пустой», а обычный enum, и из этого следует почти всё поведение.
Optional<Wrapped>— перечисление с двумя кейсами:.noneи.some(Wrapped). Значит,nilв Swift — это не нулевой указатель, а полноценное значение типа.
Как звучит вопрос
«Что такое Optional и как он устроен внутри? Чем if let отличается от guard let? Когда допустим force unwrap?»
Что на самом деле проверяют
Понимаете ли вы, что система типов Swift отделяет «значение отсутствует» от «значение есть», и умеете ли писать код, который не падает в проде. Плюс отдельный маркер зрелости: кандидат, который говорит «force unwrap использовать нельзя никогда», знает правило, но не понимает его смысла.
Устройство изнутри
// примерно так объявлен Optional в стандартной библиотеке
enum Optional<Wrapped> {
case none
case some(Wrapped)
}
let a: Int? = nil
let b: Optional<Int> = .none
print(a == b) // true — это один и тот же тип
switch a {
case .some(let value): print("есть \(value)")
case .none: print("нет значения")
}
Вывод:
true
нет значения
Отсюда следует, что Int? и Int — разные типы, и компилятор не даст сложить их по ошибке. Именно это и есть «безопасность» опционалов, а не какая-то магия рантайма.
Распаковка: if let против guard let
// if let: переменная живёт внутри блока
func describe(_ raw: String?) -> String {
if let raw, !raw.isEmpty { // сокращённый синтаксис Swift 5.7+
return "значение: \(raw)"
}
return "пусто"
}
// guard let: early exit, переменная живёт дальше по функции
func loadUser(id: String?) throws -> User {
guard let id, !id.isEmpty else {
throw AppError.badInput
}
let user = try storage.fetch(id) // id доступен здесь
return user
}
Практическое отличие — область видимости и форма кода. guard отсекает невалидные случаи сверху и оставляет «счастливый путь» на нулевом уровне вложенности, поэтому в функциях с 3–4 проверками он читается заметно лучше, чем лесенка из if let. Обязательное требование: ветка else у guard должна выйти из области — return, throw, break, continue или fatalError().
Инструменты вокруг опционалов
let nickname: String? = nil
let title = nickname ?? "аноним" // nil-coalescing, тип String
struct Profile { var address: Address? }
struct Address { var city: String }
let profile: Profile? = nil
let city = profile?.address?.city // String? — цепочка снова опционал
let cityOrDash = profile?.address?.city ?? "—"
let number = try? Int("42", format: .number) // ошибка -> nil
let view = someObject as? UIView // неудачный каст -> nil
Optional chaining возвращает опционал, даже если само свойство неопционально: результата может не быть, потому что цепочка оборвалась. Это частая точка непонимания на собеседовании.
Двойной опционал
let flags: [String: Int?] = ["retries": 3, "timeout": nil]
let raw = flags["timeout"] // Int?? — .some(.none)
let flat = flags["timeout"] ?? nil // Int? — уже .none
print(raw == nil, flat == nil)
Вывод:
false true
Внешний слой отвечает на вопрос «есть ли такой ключ», внутренний — «какое значение лежит по ключу». Схлопывают слои через ?? nil или flatMap; на последовательностях ту же роль играет compactMap:
let ids = ["1", "два", "3"]
let numbers = ids.compactMap(Int.init) // [1, 3], тип [Int]
let nested: Int?? = .some(.some(7))
let flattened: Int? = nested.flatMap { $0 } // 7
IUO и force unwrap
Тип T! (implicitly unwrapped optional) — это тот же Optional, но компилятор молча распаковывает его при использовании. Он остался в языке ради двух вещей: @IBOutlet, которые заполняются при загрузке из storyboard уже после init, и импортированных Objective-C API без аннотаций nullability. Опасность в том, что падение произойдёт в точке использования, а причина — в том, что кто-то не проинициализировал свойство.
Force unwrap ! честно оправдан там, где nil означает баг программиста, а не состояние данных: ресурс, лежащий в бандле приложения, регулярка с литеральным паттерном, значение сразу после явной проверки. Формулировка для интервьюера: «падаю намеренно и громко, потому что продолжать работу с испорченным инвариантом хуже, чем упасть». А вот данные из сети, ввод пользователя и результаты парсинга форсить нельзя никогда.
Типичные ошибки кандидатов
- Говорить, что
nil— это указатель на ноль, как в C или Objective-C. - Считать, что
guard letиif letотличаются только «стилем», не упоминая область видимости и early exit. - Забывать, что optional chaining возвращает опционал.
- Путать
mapиcompactMapпри разборе массива опционалов. - Не знать про двойной опционал в словаре со значением-опционалом.
- Отвечать «force unwrap запрещён всегда», не отличая баг программиста от состояния данных.
Как ответить кратко
Optional — это enum с кейсами
.noneи.some(Wrapped), поэтому nil в Swift не указатель, а значение типа, и компилятор не даст перепутатьInt?сInt. Распаковываю черезif let, когда значение нужно только внутри блока, и черезguard let, когда это early exit и переменная нужна дальше по функции. Рядом есть??, optional chaining,try?иas?— они тоже возвращают опционал. Force unwrap оставляю только там, где nil означает баг программиста: ресурс в бандле, литеральная регулярка. Для данных из сети и от пользователя — только безопасная распаковка.