Обработка ошибок в сетевых запросах в iOS

Обработка ошибок в сетевых запросах – критически важная часть разработки надежных iOS-приложений. Некорректная обработка может привести к крашам, неожиданному поведению или неверным данным, отображаемым пользователю. Существует несколько подходов и технологий для эффективной обработки ошибок, которые я буду рассматривать ниже.


1. URLSession: Основной инструмент

URLSession – это основной API в iOS для выполнения сетевых запросов. Он предоставляет гибкий механизм для работы с различными типами запросов (GET, POST, PUT, DELETE и т.д.) и обработки ответов.

1.1 DataTask

Наиболее распространенный способ использования URLSession – через DataTask. Он позволяет загружать данные асинхронно.

let url = URL(string: "https://example.com/api/data")!
let task = URLSession.shared.dataTask(with: url) { (data, response, error) in
    if let error = error {
        print("Ошибка запроса: \(error)")
        // Обработка ошибки здесь
    } else if let data = data {
        do {
            let decoder = JSONDecoder()
            let result = try decoder.decode(YourDataType.self, from: data)
            // Обработка успешного ответа
        } catch {
            print("Ошибка декодирования JSON: \(error)")
            // Обработка ошибки декодирования здесь
        }
    } else if let response = response {
        print("Статус код ответа: \((response as? HTTPURLResponse)?.statusCode ?? 0)")
        //Обработка статуса кода ответа, например, 404 или 500
    }
}

task.resume()

В этом примере:

  • error содержит ошибку сетевого запроса (например, отсутствие сети, таймаут).
  • data содержит данные ответа сервера.
  • response содержит информацию об ответе сервера, включая статус код.

1.2 Обработка ошибок в completion handler

Внутри completion handler необходимо тщательно проверять все возможные ошибки:

  • Сетевые ошибки: Отсутствие сети, таймаут, проблемы с DNS и т.д. Эти ошибки обычно представлены объектом Error.
  • Ошибки HTTP-статусов: Статус коды вне диапазона 200-299 указывают на ошибку (например, 404 Not Found, 500 Internal Server Error).
  • Ошибки декодирования JSON: Если сервер возвращает JSON, необходимо убедиться, что данные корректны и соответствуют ожидаемой структуре. Используйте блок do-catch для обработки ошибок декодирования.

2. Типы ошибок

Важно классифицировать ошибки для более эффективной обработки:

  • Transient Errors (Временные ошибки): Ошибки, которые могут быть повторены позже (например, таймаут сети). Рекомендуется реализовать механизм повторных попыток.
  • Permanent Errors (Постоянные ошибки): Ошибки, которые не могут быть исправлены повторной попыткой (например, 404 Not Found). Необходимо уведомить пользователя о проблеме и предложить альтернативное действие.

3. Обработка ошибок HTTP-статусов

HTTPURLResponse предоставляет доступ к статусу кода ответа сервера.

Статус кодОписаниеОбработка
200 OKУспешный запросОбработать данные ответа.
400 Bad RequestНекорректный запросПроверить параметры запроса, уведомить пользователя о некорректных данных.
401 UnauthorizedТребуется аутентификацияПеренаправить пользователя на страницу входа или обновить токен доступа.
403 ForbiddenНет прав доступаУведомить пользователя о недостатке прав доступа.
404 Not FoundРесурс не найденУведомить пользователя, что ресурс не существует.
500 Internal Server ErrorОшибка сервераПовторить запрос позже или уведомить службу поддержки.

4. Повторные попытки (Retry)

Для временных ошибок рекомендуется реализовать механизм повторных попыток.


func performRequestWithRetry(url: URL, maxRetries: Int = 3) {
    var retryCount = 0
    let task = URLSession.shared.dataTask(with: url) { (data, response, error) in
        if let error = error {
            retryCount += 1
            if retryCount <= maxRetries { print("Повторная попытка \(retryCount)...")
                self.performRequestWithRetry(url: url, maxRetries: maxRetries) // Рекурсивный вызов
            } else {
                print("Ошибка запроса после нескольких повторных попыток: \(error)") // Обработка ошибки здесь
            }
        } else { 
          // Обработка успешного ответа  
        }
    } task.resume()
}

Важно:

  • Ограничить количество повторных попыток, чтобы избежать бесконечного цикла.
  • Использовать экспоненциальную задержку между попытками (например, 1 секунда, 2 секунды, 4 секунды).

5. Использование Result type

Result type (доступен в Swift 5.0 и выше) позволяет более элегантно обрабатывать ошибки.

enum APIError: Error {
    case networkError(Error)
    case decodingError(DecodingError)
}

func fetchData() -> Result<YourDataType, APIError> {
    let url = URL(string: "https://example.com/api/data")!
    let task = URLSession.shared.dataTask(with: url) { (data, response, error) in
        if let error = error {
            return .failure(.networkError(error))
        } else if let data = data {
            do {
                let decoder = JSONDecoder()
                let result = try decoder.decode(YourDataType.self, from: data)
                return .success(result)
            } catch {
                return .failure(.decodingError(error))
            }
        } else {
            return .failure(.networkError(error ?? NSError(domain: "Unknown", code: 0))) // Обработка случая, когда error == nil, но data тоже nil.
        }
    }
    
    task.resume()
    // Необходимо использовать замыкание или completion handler для возврата Result, так как DataTask асинхронный. В данном примере это упрощено для демонстрации концепции.
    return .failure(.networkError(NSError(domain: "Not implemented", code: 0))) // В реальном коде необходимо реализовать механизм ожидания завершения task и возврата Result.
}

// Использование:
let result = fetchData()
switch result {
case .success(let data):
    print("Данные получены: \(data)")
case .failure(let error):
    print("Ошибка: \(error)")
}

Result type позволяет избежать вложенных completion handler и сделать код более читаемым.


6. Логирование ошибок

Важно логировать все ошибки сетевых запросов для отладки и мониторинга приложения.

  • Использовать инструменты логирования (например, os_log).
  • Логировать информацию об ошибке, URL запроса, HTTP-статус кода.

7. Обработка ошибок в UI

Необходимо корректно отображать ошибки пользователю:

  • Использовать понятные сообщения об ошибках.
  • Предлагать альтернативные действия (например, повторить запрос, обратиться в службу поддержки).
  • Избегать технических деталей в сообщениях об ошибках.

В заключение, обработка ошибок в сетевых запросах – это многогранный процесс, требующий внимательного подхода и использования различных технологий и техник. Тщательная обработка ошибок поможет создать надежное и стабильное iOS-приложение.


Будет интересно

Nikulux в Telegram

Статьи, архивы материалов, скоро ещё и мемы

(= Подписаться =)