Опциональное значение (или опциональное поле) — это значение, которого может не быть. В Swift такое значение описывает тип Optional: переменная типа String? содержит либо строку, либо nil — признак того, что значения нет. Обычная переменная String не может содержать nil (пустая строка "" — это значение, а не его отсутствие), и компилятор не даст использовать String? как String, пока вы явно не распакуете значение.
Содержание
- Мини-словарь
- Минимальный пример: опциональное поле в структуре
- Почему нельзя просто сложить Int и Int?
- Способы распаковки и как выбрать
- Force unwrap и ошибка «Unexpectedly found nil»
- Неявно распакованный опционал: String!
- Optional — это перечисление
- Опциональное поле в JSON: Codable
- Выводы
- Где применяется / связь с практикой
- FAQ
Ниже — как объявить опционал, пять способов достать из него значение (if let, guard let, ??, optional chaining, !), почему падает программа с ошибкой «Unexpectedly found nil» и что такое неявно распакованный опционал String!. Примеры проверены на Swift 6.3.2 (macOS, 24.09.2026).
Мини-словарь
| Термин | Что это | Пример |
|---|---|---|
| Опционал | Тип «значение или ничего», полная запись Optional<T>, краткая T? |
var age: Int? |
nil |
Отсутствие значения у опционала | age = nil |
| Распаковка (unwrapping) | Получение значения типа T из T? |
if let age { ... } |
| Неявно распакованный опционал | T!: опционал, который подставляется как T без явной распаковки |
var label: UILabel! |
Важно не путать: T? и Optional<T> — одно и то же, а T! — не «короткая запись опционала», а отдельный режим использования того же опционала (разбор ниже).
Минимальный пример: опциональное поле в структуре
Типичный случай — поле модели, которое заполнено не у всех: отчество, телефон, дата окончания подписки. Такое поле объявляют с ?:
struct User {
let name: String
let middleName: String? // опциональное поле: отчества может не быть
}
let users = [
User(name: "Анна", middleName: "Сергеевна"),
User(name: "Tom", middleName: nil),
]
for user in users {
// 1. if let: работаем со значением, только если оно есть
if let middle = user.middleName {
print("\(user.name): отчество \(middle)")
} else {
print("\(user.name): отчества нет")
}
// 2. ??: подставляем значение по умолчанию
let shown = user.middleName ?? "-"
print(" в анкете: \(shown)")
// 3. optional chaining: результат тоже опционал
let letters = user.middleName?.count
print(" букв: \(String(describing: letters))")
}
Вывод:
Анна: отчество Сергеевна
в анкете: Сергеевна
букв: Optional(9)
Tom: отчества нет
в анкете: -
букв: nil
Обратите внимание на третью строку каждого блока: user.middleName?.count возвращает не Int, а Int?. Поэтому для Анны напечатано Optional(9), а для Tom — nil, без падения.
Почему нельзя просто сложить Int и Int?
Опционал — отдельный тип, и компилятор проверяет это до запуска. Неверный код:
let base: Int = 100
let bonus: Int? = 20
let total = base + bonus
print(total)
Результат — ошибка компиляции (swiftc 6.3.2):
error: value of optional type 'Int?' must be unwrapped to a value of type 'Int'
note: coalesce using '??' to provide a default when the optional value contains 'nil'
note: force-unwrap using '!' to abort execution if the optional value contains 'nil'
Компилятор сам предлагает два пути: ?? (значение по умолчанию) или ! (принудительная распаковка). Исправление, в котором нет риска падения:
let base: Int = 100
let bonus: Int? = 20
let total = base + (bonus ?? 0)
print(total) // 120
if let bonus {
print(base + bonus) // 120
}
Похожая ошибка возникает при var s = nil: компилятор пишет 'nil' requires a contextual type, потому что по одному nil нельзя понять, опционал какого типа нужен. Правильно — указать тип: var s: String? = nil.
Способы распаковки и как выбрать
| Способ | Синтаксис | Если nil |
Когда брать |
|---|---|---|---|
| Optional binding | if let x = opt { } |
Выполняется else |
Есть действие для обоих случаев |
| Ранний выход | guard let x = opt else { return } |
Выход из функции/цикла | Без значения дальше работать нельзя |
| Значение по умолчанию | opt ?? значение |
Берется значение справа | Есть осмысленный дефолт |
| Optional chaining | opt?.свойство |
Вся цепочка дает nil |
Нужно пройти по цепочке, не распаковывая каждое звено |
| Принудительная распаковка | opt! |
Программа аварийно завершается | Только когда nil означает ошибку программиста |
if let и сокращенная форма
if let проверяет опционал и, если значение есть, кладет его в новую константу обычного типа. Эта константа видна только внутри фигурных скобок if.
Начиная со Swift 5.7 можно не повторять имя: if let nickname { ... } — то же, что if let nickname = nickname { ... }. Несколько опционалов проверяются через запятую, и тело выполнится, только если значения есть у всех:
let nickname: String? = "swiftie"
if let nickname { // Swift 5.7+: то же, что if let nickname = nickname
print("Ник: \(nickname.uppercased())")
}
let a: Int? = 3
let b: Int? = nil
if let a, let b {
print(a + b)
} else {
print("одного из чисел нет")
}
Выведет Ник: SWIFTIE и одного из чисел нет.
guard let: ранний выход
guard let удобен в функциях: если значения нет, выходим сразу, а распакованная константа остается доступной до конца функции (в отличие от if let). В условие можно добавить проверку диапазона:
func parseAge(_ text: String) -> Int? {
guard let age = Int(text), (0...150).contains(age) else {
print("'\(text)': не возраст")
return nil
}
// age здесь уже Int и доступен до конца функции
return age
}
print(parseAge("42") as Any)
print(parseAge("-5") as Any)
print(parseAge("сорок") as Any)
Optional(42)
'-5': не возраст
nil
'сорок': не возраст
nil
Здесь видно, откуда опционалы берутся в реальном коде: Int("сорок") не бросает исключение, а возвращает nil. Так же ведут себя поиск в словаре (dict["key"]), array.first, try? и многие API Apple.
Оператор ??
opt ?? default возвращает значение опционала или, если там nil, значение справа. Если справа обычное значение, тип результата — уже не опционал; если справа тоже опционал (a ?? b, где оба Int?), результат останется Int?. Дефолт должен быть осмысленным: age ?? 0 превращает «возраст неизвестен» в «ноль лет», и дальше эта подмена может попасть в статистику. Если отличить «нет значения» от нуля важно — используйте if let.
Force unwrap и ошибка «Unexpectedly found nil»
Восклицательный знак после опционала (opt!) говорит компилятору: «здесь точно есть значение». Если его нет, программа падает во время выполнения. Неверный код:
let input = "12a"
let number = Int(input)!
print(number)
Результат запуска (сборка без оптимизаций, режим Debug):
Fatal error: Unexpectedly found nil while unwrapping an Optional value
Процесс завершается с кодом 133 (сигнал SIGTRAP). В сборке с -O (как в Release) программа тоже падает, но в нашем прогоне текст сообщения в консоль не выводился — только аварийное завершение. В iOS-приложении это вылет у пользователя.
Исправление — обработать оба случая:
let input = "12a"
if let number = Int(input) {
print("Число: \(number)")
} else {
print("'\(input)' - не целое число")
}
Вывод: '12a' - не целое число.
Прием if x != nil { x! } работает, но проверку и распаковку легко рассинхронизировать при правке; if let делает оба действия сразу. Когда ! уместен: значение гарантировано логикой программы, и его отсутствие — баг, который лучше увидеть сразу (например, ресурс, встроенный в приложение). Даже тогда многие команды предпочитают guard let ... else { fatalError("понятное сообщение") }, чтобы в логе было видно, что именно сломалось.
Неявно распакованный опционал: String!
String! — это опционал Optional<String>, который компилятор разрешает использовать как String без явной распаковки. Проверки при этом нет: если внутри nil, будет то же падение.
let iuo: String! = "hello"
let n: Int = iuo.count // используется как String без распаковки
let copy = iuo // тип выводится как String?, а не String
print(n, type(of: copy))
Вывод: 5 Optional<String>. При var t: String! = nil вызов t.count падает с сообщением Fatal error: Unexpectedly found nil while implicitly unwrapping an Optional value.
Где это встречается: свойства, которые заполняются не в инициализаторе, а позже, но гарантированно до использования. Классический пример — @IBOutlet weak var titleLabel: UILabel! в UIKit: связь с элементом интерфейса появляется после загрузки экрана. В собственном коде T! лучше не заводить без такой причины.
Optional — это перечисление
Под капотом Optional<T> — обычный enum с двумя вариантами: .some(значение) и .none (это и есть nil). Поэтому опционал можно разобрать через switch:
switch Int("7") {
case .some(let value): print("есть значение \(value)")
case .none: print("пусто")
}
Вывод: есть значение 7. Это объясняет, почему nil в Swift — не «нулевой указатель», как в C, а значение типа: опционалом может быть и Int, и структура, и класс.
Опциональное поле в JSON: Codable
С опциональными полями чаще всего сталкиваются при разборе ответа сервера. Если поле модели объявлено как String?, JSONDecoder при отсутствии ключа (или значении null) запишет nil, а не упадет:
import Foundation
struct Profile: Codable {
let login: String
let phone: String? // опциональное поле JSON
}
let json = #"[{"login": "anna", "phone": "+7 900 000-00-00"}, {"login": "tom"}]"#
let profiles = try JSONDecoder().decode([Profile].self, from: Data(json.utf8))
for p in profiles {
print(p.login, p.phone ?? "телефон не указан")
}
let broken = #"[{"phone": "123"}]"#
let result = try? JSONDecoder().decode([Profile].self, from: Data(broken.utf8))
print(result as Any)
anna +7 900 000-00-00
tom телефон не указан
nil
Во втором случае нет обязательного login, декодер бросает ошибку, а try? превращает ее в nil. Граница: try? теряет причину ошибки. Для отладки и логов используйте do { try ... } catch { print(error) }. Еще одно ограничение: String? не спасает, если ключ пришел с другим типом (например, число вместо строки) — это тоже ошибка декодирования.
Выводы
- Опциональное значение — то, которого может не быть; в Swift это тип
Optional<T>(краткая записьT?), отсутствие значения обозначаетсяnil. - Компилятор не даст использовать
T?какT: сначала распаковка черезif let,guard let,??или optional chaining. opt!приnilаварийно завершает программу с ошибкой «Unexpectedly found nil while unwrapping an Optional value»; его оставляют для случаев, когдаnilозначает баг.T!— тот же опционал с автоматической распаковкой, без защиты от падения; типичное место —@IBOutlet.- Для опциональных полей в моделях и JSON объявляйте поле
T?и решайте, что показывать приnil, явно.
Где применяется / связь с практикой
Освойте тему на практике
Опционалы встречаются в каждом iOS-приложении: ответы сервера с необязательными полями, поиск по словарю, преобразование ввода пользователя, элементы интерфейса. Большая часть вылетов у начинающих — это ! там, где значение на самом деле может отсутствовать. Системно разобрать Swift, UIKit и SwiftUI на учебном проекте можно на курсе по iOS-разработке с нуля. Отдельные темы iOS-разработки в формате живого занятия с преподавателем разбирают на открытых уроках Otus.
FAQ
Чем nil в Swift отличается от null в других языках?
В Swift nil может быть только у опционала, и компилятор требует его обработать. В Kotlin похожая модель (String?), в C# 8+ nullable reference types дают предупреждения, а не ошибки компиляции.
Можно ли сравнить опционал с обычным значением без распаковки?
Да, == работает: Int?(3) == 3 дает true, а опционал, в котором nil, при сравнении с 3 дает false. Для арифметики распаковка все равно нужна.
Что делает ?. при вызове метода, который ничего не возвращает?
Метод выполнится, только если значение есть; результат выражения будет Void?, и по нему можно проверить, был ли вызов: if user?.save() != nil { ... }.



