Do-Catch: что это, конструкция и обработка ошибок в Swift

Автор: IT Sectr Опубликовано: 2026-05-26 Время чтения: 8 мин

Do-Catch — конструкция языка Swift для обработки ошибок, которая перехватывает исключения, выброшенные через throw, и выполняет соответствующий блок кода. В отличие от традиционных try-catch в других языках, Swift требует явной пометки функций, которые могут выбросить ошибку, модификатором throws. По данным Swift.org, 2026, Do-Catch — основной механизм обработки сбоев в Swift, обеспечивающий безопасность на уровне типов.

Главное

  • Do-Catch — конструкция Swift, перехватывающая ошибки из блоков с try и throw.
  • throws — маркер функции, сигнализирующий, что она может выбросить ошибку.
  • try — ключевое слово для вызова throwing-функции внутри do-блока.
  • catch — блок обработки ошибки с возможностью сопоставления по типу.
  • try? и try! — альтернативные формы, преобразующие ошибку в nil или вызывающие crash.

Что такое Do-Catch?

Do-Catch — это конструкция в Swift, состоящая из блока do, внутри которого выполняется код, способный выбросить ошибку, и одного или нескольких блоков catch, обрабатывающих эту ошибку. Блок do содержит вызовы throwing-функций, помеченные try, а catch-блоки сопоставляют ошибку по типу.

Swift реализует модель структурированной обработки ошибок, отличную от исключений в Objective-C. Здесь ошибка — не exception с раскруткой стека, а значение, соответствующее протоколу Error. Бросок ошибки через throw передаёт управление ближайшему catch-блоку без накладных расходов на раскрутку.

Ключевая особенность Swift — явность обработки. Компилятор не позволяет вызвать throwing-функцию без try, do-catch или альтернативного механизма (try?, try!). Это исключает ситуации, когда ошибка остаётся незамеченной.

Модель ошибок в Swift: Error протокол и throwing функции

В основе обработки ошибок в Swift лежит протокол Error. Любой тип, соответствующий этому протоколу, может быть выброшен через throw. Обычно ошибки определяют как enum с подтверждением Error.

Протокол Error

Error — пустой протокол (маркерный). Он не требует реализации методов, только подтверждения соответствия. Компилятор Swift использует Error для статической проверки: функция с throws может выбросить только значение, соответствующее Error.

Throwing функции

Функция, помеченная throws, обязана вызываться с try. Если внутри такой функции происходит throw, управление передаётся в вызывающий do-catch. Если ошибка не обработана нигде в цепочке вызовов, она пробрасывается на уровень выше.

swift
enum FileError: Error {
    case notFound
    case permissionDenied
    case corrupted(String)
}

func readFile(path: String) throws -> String {
    guard FileManager.default.fileExists(atPath: path) else {
        throw FileError.notFound
    }
    return try String(contentsOfFile: path)
}

Проброс ошибки rethrows

rethrows — модификатор для функций, которые принимают throwing-замыкание как параметр. Если переданное замыкание не выбрасывает ошибку, вызывает rethrows-функцию можно без try. Это позволяет писать обобщённые функции высшего порядка, совместимые с throwing и non-throwing замыканиями.

Синтаксис Do-Catch: несколько блоков catch

Swift поддерживает множественные catch-блоки с сопоставлением по типу ошибки. Это похоже на when в Kotlin или switch по типу: первый подходящий catch выполняется, остальные пропускаются.

Сопоставление по типу

Каждый catch-блок может содержать шаблон сопоставления с конкретным типом ошибки. Swift проверяет блоки по порядку, поэтому более специфичные типы должны располагаться раньше общих. Если ни один шаблон не подошёл, выполняется catch-блок без шаблона.

Обработка нескольких типов

Типобезопасность Do-Catch позволяет обрабатывать каждую ошибку отдельно или объединять несколько типов в одном блоке через запятую в pattern-matching. Это устраняет громоздкие if-else цепочки, характерные для Objective-C, где анализ ошибки выполнялся через код возврата или NSError с проверкой domain и code.

Swift также поддерживает catch where — сопоставление с дополнительным условием. Например, можно отфильтровать ошибки по конкретному коду или тексту: catch URLError where error.code == .notConnectedToInternet. Это сокращает количество catch-блоков при сохранении точности обработки.

Для отладки throwing-функций Swift предоставляет разницу между ошибками и исключениями. Ошибка Swift — это возвращаемое значение, которое не требует раскрутки стека. Исключения Objective-C (@try/@catch) работают на уровне runtime и используются только для фатальных сбоев. Do-Catch обрабатывает именно ошибки, а не исключения, что делает его предсказуемым и производительным.

При разработке библиотек и SDK важно помнить, что throwing-функции — часть публичного контракта. Тип ошибки не специфицирован в сигнатуре, поэтому документируйте, какие ошибки может выбросить функция, в комментарии или через Result-подобные enum. Это помогает пользователям API писать корректные catch-блоки без необходимости заглядывать в реализацию.

В контексте асинхронного программирования Do-Catch с async/await решает проблему callback hell. Раньше в Swift обработка ошибок в асинхронном коде требовала вложенных замыканий с проверкой Error? в completion handler. С async/await и Do-Catch асинхронный throwing-код выглядит как синхронный, что упрощает чтение и поддержку сложных цепочек запросов с обработкой ошибок на каждом шаге.

swift
do {
    let content = try readFile(path: "/data/config.json")
    process(content)
} catch FileError.notFound {
    createDefaultConfig()
} catch FileError.permissionDenied {
    requestAccess()
} catch FileError.corrupted(let detail) {
    logCorruption(detail)
} catch {
    print("Неизвестная ошибка: \(error)")
}

Try-вариации: try, try?, try! и когда их использовать

Swift предлагает три варианта вызова throwing-функций: try, try? и try!. Каждый вариант решает конкретную задачу и имеет свои ограничения.

try — стандартный вызов

try используется внутри do-блока, и ошибка обрабатывается в catch. Это основной способ вызова throwing-функций. Компилятор требует, чтобы try находился в контексте, где ошибка может быть перехвачена.

try? — преобразование в Optional

try? преобразует результат throwing-функции в Optional. Если функция выбрасывает ошибку, try? возвращает nil, иначе — Optional с успешным значением. Удобно для вызовов, где ошибку можно игнорировать, но нужен признак успеха.

try! — принудительное выполнение

try! подавляет обработку ошибок. Если throwing-функция выбрасывает ошибку, приложение завершается с runtime error. Используйте try! только когда абсолютно уверены, что ошибка невозможна — например, при загрузке встроенного ресурса.

swift
// try? — игнорируем ошибку, получаем nil при сбое
if let data = try? Data(contentsOf: url) {
    processData(data)
}

// try! — только если ошибка гарантированно невозможна
let bundled = try! String(contentsOfFile: "Assets/default.txt")

// try — стандартный вариант в do-catch
do {
    let result = try performNetworkRequest()
    updateUI(result)
} catch {
    showError(error)
}

Do-Catch в iOS-разработке: практические примеры

Do-Catch активно используется в iOS-разработке для работы с системными API. Рассмотрим типовые сценарии: работа с файловой системой, Core Data и сетевые запросы.

Файловая система

Многие методы FileManager являются throwing. Do-Catch позволяет корректно обработать отсутствие файла, занятость ресурса или недостаток прав.

Сетевые запросы с Codable

Парсинг JSON через JSONDecoder — throwing операция. Do-Catch перехватывает ошибки декодирования и сетевые сбои раздельно, предоставляя пользователю точный текст ошибки.

swift
struct User: Codable {
    let id: Int
    let name: String
}

func fetchUser(id: Int) async throws -> User {
    let url = URL(string: "https://api.example.com/users/\(id)")!
    let (data, _) = try await URLSession.shared.data(from: url)
    return try JSONDecoder().decode(User.self, from: data)
}

// Вызов с обработкой
do {
    let user = try await fetchUser(id: 42)
    showUser(user)
} catch let error as URLError {
    showNetworkAlert(error)
} catch let error as DecodingError {
    showParseError(error)
} catch {
    showGenericError(error)
}

Типичные ошибки при работе с Do-Catch

Разработчики, особенно переходящие с других языков, часто допускают характерные ошибки при использовании Do-Catch. Рассмотрим наиболее распространённые.

  • Забытый try — вызов throwing-функции без try приводит к ошибке компиляции. Swift не позволяет неявно игнорировать ошибки.
  • Пустой catch — catch-блок без обработки скрывает ошибку. Всегда логируйте или обрабатывайте ошибку, даже если она кажется невозможной.
  • Catch без сопоставления — один общий catch для всех ошибок лишает преимуществ типобезопасности. Разделяйте обработку по типам через несколько блоков.
  • Чрезмерное использование try! — try! в production-коде — источник неожиданных crash. Используйте только для встроенных ресурсов или тестов.

Часто задаваемые вопросы

Чем Do-Catch в Swift отличается от try-catch в других языках?

В Swift ошибка — это значение, соответствующее протоколу Error, а не exception с раскруткой стека. Throwing-функции обязаны быть явно помечены throws, и компилятор требует try при вызове — это исключает необработанные ошибки на этапе компиляции.

Можно ли использовать Do-Catch с async/await?

Да, async функции также могут быть throws, и для них работает do-catch. Вызов async throwing функции требует try await внутри do-блока. Это стандартный способ обработки ошибок в асинхронном коде Swift.

Как обработать ошибку без do-catch?

Есть альтернативы: try? преобразует ошибку в nil, try! вызывает crash при ошибке, а rethrows позволяет пробрасывать ошибку из замыкания. Do-catch остаётся основным способом явной обработки.

Может ли одна функция выбрасывать разные типы ошибок?

Swift не типизирует выбрасываемые ошибки — любая throwing-функция может выбросить любой тип, соответствующий Error. Разделяйте обработку через сопоставление в catch по конкретным типам ошибок.

Стоит ли оборачивать все ошибки в свой кастомный тип?

Да, это лучшая практика. Определите AppError как enum с подтверждением Error и конвертируйте системные ошибки в доменные. Это унифицирует обработку и изолирует слой приложения от системных деталей.

Итоги

  • Do-Catch — конструкция Swift для обработки ошибок с обязательной пометкой throws на функциях и try при вызове.
  • Ошибки Swift — значения, соответствующие протоколу Error, а не исключения с раскруткой стека, что даёт нулевые накладные расходы при отсутствии сбоя.
  • Множественные catch позволяют обрабатывать разные типы ошибок раздельно с pattern-matching, аналогичным switch.
  • try? преобразует ошибку в nil для простых сценариев, try! — принудительный вызов с риском crash, используемый только для гарантированно успешных операций.
  • Do-Catch с async/await — стандартный способ обработки ошибок в асинхронном Swift коде с использованием try await.
  • Избегайте пустых catch и чрезмерного использования try! — это скрывает ошибки и приводит к неожиданным сбоям в production.

Мы разработаем мобильное приложение под ключ

IT Sectr создаёт приложения для iOS и Android для стартапов и бизнеса с 2017 года. Мы проконсультируем вас и предложим наилучшее решение.

Обсудить проект

Читайте также