Обработка ошибок в Swift: try/catch и не только
Разработка под iOS и macOS требует надёжного управления ошибками. Swift предоставляет мощный и элегантный механизм обработки сбоев, основанный на ключевых словах throws, try, catch и do. В этой статье мы разберём, как правильно обрабатывать ошибки, писать читаемый код и избегать распространённых ловушек.
1. Основы: протокол Error и ключевое слово throws
Любая ошибка в Swift должна соответствовать протоколу Error. Обычно для этого используют перечисления (enum):
enum NetworkError: Error {
case badURL
case noData
case decodingFailed(String)
}
Функция, которая может выбросить ошибку, помечается ключевым словом throws:
func fetchUser(from urlString: String) throws -> User {
guard let url = URL(string: urlString) else {
throw NetworkError.badURL
}
// ... логика запроса
}
Теперь при вызове такой функции компилятор обяжет вас обработать возможную ошибку.
2. Конструкция do-catch: перехват ошибок
Самый распространённый способ обработки — блок do-catch. Вы помещаете потенциально опасный код в do, а в catch обрабатываете сбой:
do {
let user = try fetchUser(from: "https://api.example.com/user")
print("User loaded: \(user.name)")
} catch NetworkError.badURL {
print("Неверный URL")
} catch NetworkError.noData {
print("Сервер не вернул данные")
} catch NetworkError.decodingFailed(let message) {
print("Ошибка декодирования: \(message)")
} catch {
print("Неизвестная ошибка: \(error.localizedDescription)")
}
Важно: последний блок catch без конкретного типа ловит все оставшиеся ошибки — его стоит добавлять всегда.
2.1. Сопоставление с образцом в catch
Вы можете использовать более сложные условия:
do {
let result = try riskyOperation()
} catch let error as NetworkError where error == .badURL {
print("Проблема с URL")
} catch {
print(error)
}
3. Альтернативы: try? и try!
Не всегда нужна полная обработка. Swift предлагает два упрощённых подхода:
3.1. try? — опциональное значение
Если ошибка не критична, можно преобразовать результат в опционал. При сбое функция вернёт nil:
let user = try? fetchUser(from: "https://example.com/user")
// user имеет тип User? — nil при ошибке
Идеально для ситуаций, где ошибку можно просто проигнорировать (например, загрузка аватара).
3.2. try! — принудительное выполнение
Используйте try! только когда абсолютно уверены, что ошибки не будет. Иначе — краш приложения:
let user = try! fetchUser(from: "https://valid-url.com/user")
// Если URL невалиден — приложение упадёт
Применяйте try! в тестах или в ситуациях, где сбой невозможен по логике программы.
4. Продвинутые техники: rethrows и deferred
4.1. rethrows для функций высшего порядка
Если ваша функция принимает замыкание, которое может выбросить ошибку, используйте rethrows. Это позволяет не помечать собственную функцию как throws, если замыкание не выбрасывает ошибку:
func process(_ closure: () throws -> Void) rethrows {
try closure()
}
// Вызов без throws-замыкания — не требует do-catch
process { print("Без ошибок") }
// Вызов с throws-замыканием — требует обработки
try process { throw NetworkError.noData }
4.2. deferred: гарантированная очистка
Блок defer выполняется при любом завершении функции (успех или ошибка). Отлично подходит для закрытия файлов или освобождения ресурсов:
func readFile() throws {
let file = openFile()
defer { closeFile(file) } // выполнится в любом случае
// ... операции с файлом, которые могут выбросить ошибку
}
5. Типичные ошибки и рекомендации
- Не используйте try! без крайней необходимости — это источник неожиданных крашей.
- Избегайте пустых catch-блоков — они скрывают ошибки и усложняют отладку.
- Структурируйте ошибки — используйте enum с associated values для передачи контекста.
- Не забывайте про localizedDescription — для показа пользователю понятных сообщений.
Заключение
Обработка ошибок в Swift — это не просто синтаксический сахар, а продуманная система, которая делает код безопаснее и читаемее. Используйте throws для обозначения опасных функций, do-catch для гибкой обработки, а try? и try! — только в уместных сценариях. Следуя этим принципам, вы сможете писать надёжные приложения для iOS и macOS, которые корректно реагируют на любые сбои.