Урок по треку Swift и iOS 6 мин

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

throws, do/catch, try? и try!, Result, typed throws, defer и rethrows.

В Swift ошибки это значения, а не исключения в стиле C++ или Java. На собесе спросят про try? и try!, порядок defer, Result, typed throws и стратегию ошибок в приложении.

Объявление и бросание

Ошибка это любой тип, соответствующий протоколу Error. Обычно enum со связанными значениями:

enum LoginError: Error {
    case wrongPassword
    case blocked(minutes: Int)
}

func login(_ email: String, _ password: String) throws -> Session {
    guard password.count >= 8 else { throw LoginError.wrongPassword }
    ...
}

Вызов и обработка

Каждая точка, где может вылететь ошибка, помечается try:

do {
    let session = try login(email, password)
    show(session)
} catch LoginError.blocked(let minutes) {
    showBlocked(minutes)
} catch {
    showError(error)   // неявная константа error
}

catch принимает паттерны, как case в switch, включая let e as DecodingError.

Три формы try:

  • try: ошибка пробрасывается выше (вызывающая функция тоже throws) или ловится в do/catch;
  • try?: результат становится опционалом, ошибка теряется;
  • try!: при ошибке приложение падает.

С Swift 5 try? не создаёт двойной опционал: для функции, возвращающей Int?, результат Int?.

defer

defer откладывает блок до выхода из текущей области по любому пути: return, throw, break. Несколько defer выполняются в обратном порядке.

func work() -> Int {
    var x = 1
    defer { print("defer 1", x) }
    defer { x += 10; print("defer 2") }
    x = 5
    return x       // вернёт 5: значение вычислено до defer
}
// defer 2
// defer 1 15

defer внутри do выполнится при выходе из do, то есть до блока catch.

rethrows

Функция с rethrows бросает, только если бросает переданное замыкание. Поэтому map { $0 * 2 } не требует try, а map { try parse($0) } требует.

Result

Result<Success, Failure> делает результат значением, которое можно сохранить и передать дальше. Удобно в callback-API:

func load(completion: @escaping (Result<User, Error>) -> Void)

let r = Result { try decoder.decode(User.self, from: data) }
let user = try r.get()

В синхронном и async-коде идиоматичнее throws.

Typed throws (Swift 6)

func parse(_ s: String) throws(ParseError) -> Int

В catch переменная error сразу типа ParseError, функция не может бросить другой тип. Обычный throws равен throws(any Error), а throws(Never) значит «не бросает». Хорошо подходит для внутренних модулей и обобщённого кода, а в публичном API тип ошибки становится частью контракта, и новые случаи ломают клиентов.

Цена ошибок

Swift передаёт ошибку через выделенный регистр, как особый возвращаемый результат, без раскрутки стека по таблицам. Поэтому бросание дешёвое, и ошибки нормальная часть потока управления.

assert, precondition, fatalError

Debug (-Onone)Release (-O)
assertпроверяетсявыброшен
preconditionпроверяетсяпроверяется
fatalErrorвсегдавсегда

fatalError возвращает Never, поэтому годится как выход из guard. Эти функции для ошибок программиста, а не для данных извне.

Ошибки для пользователя

Реализуйте LocalizedError с errorDescription, иначе localizedDescription вернёт малополезный системный текст. Отмену (CancellationError, URLError.cancelled) пользователю не показывают.

Стратегия в приложении

  1. Сетевой слой бросает технические ошибки: нет сети, таймаут, статус, декодирование.
  2. Репозиторий переводит их в доменные: «сессия истекла», «нет связи».
  3. View model решает, как показать: экран ошибки с «Повторить», тост поверх кеша или ничего.
  4. Всё логируется с контекстом, без персональных данных.

Частые ошибки

  • try? при декодировании: непонятно, какое поле сломалось.
  • try! для данных из сети.
  • Ожидать, что defer изменит уже вычисленное возвращаемое значение.
  • Показывать пользователю ошибку отмены при уходе с экрана.

Что спросят на собесе

  • Разница try, try?, try!.
  • В каком порядке выполняются defer и что вернёт функция?
  • Что такое rethrows, когда нужен Result?
  • Что дают typed throws в Swift 6?
  • Чем assert отличается от precondition?

Закрепите за 5 минут

После урока в тренажёре 3-5 проверочных вопросов, дальше модуль уходит в повторения.