Обработка ошибок в Swift
О чём эта статья
Ошибки в типах — throws, try, do-catch, try?, свой enum : Error. Отдельно от fatalError и force-unwrap.
Интерактивное демо — в Swift ошибки через
throwsиdo-catch, не классические исключения; демо показывает стек и сценарий обработки. Подробнее: ошибки и исключения.
Play ITЗагрузка интерактивного демо…
- Ошибки — это типы, соответствующие протоколу
Error. - Функции, которые могут выбрасывать ошибки, помечаются ключевым словом
throws. - Обработка ошибок осуществляется с помощью:
do-catchtry?(преобразует результат вOptional)try!(игнорирует ошибку, вызывает панику при её возникновении)
- Нет иерархии наследования: вместо этого используются перечисления (
enum) для группировки связанных ошибок.
Встроенные типы ошибок в стандартной библиотеке Swift
Swift не предоставляет большого количества предопределённых классов ошибок. Однако в Foundation и других системных фреймворках Apple определены следующие важные типы:
1. NSError (из Foundation)
- Объектно-ориентированное представление ошибки, совместимое с Objective-C.
- Содержит:
domain: String— домен ошибки (например,"NSCocoaErrorDomain")code: Int— числовой кодuserInfo: [String: Any]— дополнительные данные
- Соответствует протоколу
Error, поэтому может использоваться сthrow.
2. Системные перечисления ошибок (в Foundation и других фреймворках)
Хотя Swift сам по себе не определяет глобальных классов вроде IndexError, Apple предоставляет типизированные ошибки через перечисления:
DecodingError,EncodingError(вFoundation) — ошибки сериализации/десериализации JSON и других форматов.URLError(вFoundation) — ошибки сетевых запросов (например, отсутствие соединения, недопустимый URL).POSIXError— ошибки уровня POSIX (например,ENOENT,EACCES), представлены как перечисление с кейсами:.noSuchFileOrDirectory.permissionDenied.fileExists- и др.
CocoaError— ошибки, связанные с файловой системой, архивацией, свойствами и т.д.
Пример:
enum FileOperationError: Error {
case fileNotFound
case permissionDenied
case invalidFormat
}
Разбор:
enum ...: Errorобъявляет пользовательский тип ошибок в Swift.- Каждый
caseпредставляет отдельный доменный сценарий сбоя. - Такой формат лучше строковых ошибок: компилятор помогает обработать все варианты.
3. Ошибки времени выполнения (аварийные завершения)
Эти ситуации не являются исключениями, а вызывают панику (fatal error):
- Выход за границы массива:
Index out of range - Принудительное разворачивание
nil:Unexpectedly found nil - Деление на ноль (для целых чисел)
- Нарушение precondition/assertion
Такие ошибки нельзя перехватить с помощью do-catch.
Особенности
- Нет общего базового класса ошибок — только протокол
Error. - Перечисления (
enum) — рекомендуемый способ определения пользовательских ошибок. - Совместимость с NSError: при вызове Objective-C API ошибки автоматически преобразуются в
NSError, который соответствуетError. - Локализация: ошибки могут предоставлять локализованные описания через
LocalizedError(подпротоколError).
Пример использования
enum NetworkError: Error {
case invalidURL
case noData
case decodingFailed
}
func fetchData() throws -> Data {
guard let url = URL(string: "https://example.com") else {
throw NetworkError.invalidURL
}
// ...
}
Разбор:
NetworkErrorгруппирует типичные сетевые проблемы в одном типе.guard let url = URL(...) else { throw ... }валидирует вход до начала основной логики.throw NetworkError.invalidURLзавершает функцию с предсказуемой ошибкой, которую можно поймать вdo-catch.
Нет типов ошибок
В Swift нет фиксированного списка "типов ошибок", аналогичного Python или Java, потому что:
- Ошибки определяются как любые типы, соответствующие протоколу
Error. - Стандартная библиотека Swift почти не содержит встроенных ошибок.
- Основные системные ошибки предоставляются через Foundation в виде
NSError,URLError,DecodingError,POSIXErrorи подобных типов. - Пользовательские ошибки создаются явно, чаще всего как
enum.
Таким образом, модель ошибок в Swift — протокол-ориентированная и композиционная, без иерархии наследования.
do-catch — перехват и разбор ошибок
Базовый сценарий: функция помечена throws, вызывающий код обрабатывает конкретные и общие случаи.
Код ITЗагрузка примера кода…
Разбор:
throwsв сигнатуреparseAgeсообщает: функция может завершиться черезthrow, а не толькоreturn.guardсthrowвыполняет раннюю валидацию и не допускает "глубокой" вложенности.tryвнутриdoзапускает потенциально падающий вызов; при ошибке управление переходит вcatch.- Отдельные
catchпо типу (ParseError.emptyInput) дают точные сообщения пользователю. - Последний
catchбез типа — запасная ветка для любых другихError.
try? и try! — когда результат optional или "обязан" успеть
let ok = try? parseAge(from: "25") // Int?
let bad = try? parseAge(from: "abc") // nil
// let crash = try! parseAge(from: "abc") // runtime trap — только в тестах/гарантированных местах
Разбор:
try?превращает результат в optional: при успехе —.some(value), при ошибке —nil.- Удобно для необязательных операций ("попробовать распарсить, иначе пропустить").
- Информация о конкретной ошибке теряется — для отладки лучше
do-catch. try!принудительно разворачивает результат и падает, если была ошибка; в прикладном коде почти не используют.
Проброс ошибки вверх по стеку
Код ITЗагрузка примера кода…
Разбор:
loadConfigне перехватывает ошибкиData/JSONSerialization— пробрасывает их вызывающему черезthrows.tryвнутриthrows-функции автоматически передаёт ошибку дальше, если нет своегоcatch.bootstrap— граница приложения, где ошибка превращается в понятное действие (лог, fallback, alert).- Так разделяют "низкоуровневые" сбои и "пользовательский" уровень обработки.
LocalizedError — человекочитаемые сообщения
Код ITЗагрузка примера кода…
Разбор:
- Протокол
LocalizedErrorрасширяетErrorсвойствомerrorDescription. - UI и логи могут показывать
error.localizedDescriptionбез ручногоswitchпо enum. - Associated value
untilпозволяет включить контекст прямо в текст ошибки. - Доменная модель ошибок остаётся типобезопасной, а сообщения — локализуемыми.
Result и throws — два способа выразить сбой
Код ITЗагрузка примера кода…
Разбор:
Result<Success, Failure>кодирует исход в значении, а не через механизмthrow.- Обёртка
parseAgeResultудобна на границах, гдеthrowsнежелателен (колбэки, Combine, legacy API). switchпо.success/.failure— тот же pattern matching, что и для enum состояний.- В новом коде чаще
async throws;Resultостаётся для явных контрактов "успех или ошибка в типе".