Перейти к основному содержимому

Обработка ошибок в Swift

Разработчику Архитектору

О чём эта статья

Ошибки в типах — throws, try, do-catch, try?, свой enum : Error. Отдельно от fatalError и force-unwrap.

Опора: опционалы, switch.

Интерактивное демо — в Swift ошибки через throws и do-catch, не классические исключения; демо показывает стек и сценарий обработки. Подробнее: ошибки и исключения.

Play ITЗагрузка интерактивного демо…

  1. Ошибки — это типы, соответствующие протоколу Error.
  2. Функции, которые могут выбрасывать ошибки, помечаются ключевым словом throws.
  3. Обработка ошибок осуществляется с помощью:
    • do-catch
    • try? (преобразует результат в Optional)
    • try! (игнорирует ошибку, вызывает панику при её возникновении)
  4. Нет иерархии наследования: вместо этого используются перечисления (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, EncodingErrorFoundation) — ошибки сериализации/десериализации JSON и других форматов.
  • URLErrorFoundation) — ошибки сетевых запросов (например, отсутствие соединения, недопустимый 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 остаётся для явных контрактов "успех или ошибка в типе".