Использование URLSession с Async/Await для сетевых запросов в Swift

Использование URLSession с Async/Await для сетевых запросов в Swift

В современном Swift программировании работа с сетью становится проще и удобнее благодаря async/await. Вместо использования устаревших замыканий (completion handlers) теперь можно писать асинхронный код, который легче читать и поддерживать.

В этой статье мы разберем, как использовать URLSession для выполнения сетевых запросов с async/await, обработку ошибок и декодирование JSON-ответов.

📌 Зачем использовать async/await с URLSession?

До появления async/await работа с URLSession требовала использования замыканий, что приводило к вложенному коду (callback hell). Теперь же код становится линейным и понятным, а ошибки можно обрабатывать через try/catch.

1 Выполнение GET-запроса

Допустим, нам нужно получить данные с сервера. Для этого используем URLSession.shared.data(from:):

import Foundation /// URL-адрес для запроса let url = URL(string: "https://httpbin.org/get")! /// Выполнение сетевого запроса do { let (data, response) = try await URLSession.shared.data(from: url) print("Данные получены: \(data)") } catch { print("Ошибка запроса: \(error)") }

Что здесь происходит?

  • Мы создаем URL для запроса.
  • URLSession.shared.data(from:) выполняет запрос и возвращает данные.
  • Используем try await, чтобы дождаться результата запроса.
  • В случае ошибки catch перехватит её и выведет сообщение.

Важно! Даже если сервер вернет 404 Not Found, ошибки не будет, так как URLSession не проверяет коды ответа.

2 Добавление параметров в GET-запрос

Часто требуется передавать параметры в URL (например, ?name=John&age=30). Для этого используем URLComponents:

var urlComponents = URLComponents(string: "https://httpbin.org/get")! urlComponents.queryItems = [ URLQueryItem(name: "name", value: "John"), URLQueryItem(name: "age", value: "30") ] /// Проверяем, что URL корректный guard let url = urlComponents.url else { throw URLError(.badURL) } /// Выполнение запроса let (data, response) = try await URLSession.shared.data(from: url) print("Получен ответ: \(data)")

🔹 Что делает этот код?

  • Используем URLComponents, чтобы удобно добавлять параметры.
  • Проверяем, что urlComponents.url не nil.
  • Выполняем GET-запрос через URLSession.

3 Выполнение POST-запроса

POST-запросы обычно содержат тело (body) с JSON-данными. Рассмотрим, как отправить JSON-запрос:

/// Определяем структуру данных struct PostData: Codable { let name: String let age: Int } /// Создаем URL и URLRequest let url = URL(string: "https://httpbin.org/post")! var request = URLRequest(url: url) request.httpMethod = "POST" request.setValue("application/json", forHTTPHeaderField: "Content-Type") /// Кодируем данные в JSON let postData = PostData(name: "John", age: 30) let jsonData = try JSONEncoder().encode(postData) request.httpBody = jsonData /// Выполняем запрос let (data, response) = try await URLSession.shared.data(for: request) print("Ответ сервера: \(data)")

🔹 Что происходит в этом коде?

  • Создаем структуру PostData, которая соответствует JSON.
  • Настраиваем URLRequest: указываем httpMethod = "POST".
  • Кодируем структуру в JSON с помощью JSONEncoder().
  • Указываем заголовок Content-Type: application/json.
  • Отправляем запрос через URLSession.shared.data(for:).

4 Декодирование JSON-ответа

Допустим, сервер вернул JSON вида:

{ "json": { "name": "John", "age": 30 } }

Чтобы его обработать, создаем структуру и декодируем ответ:

/// Определяем структуру для ответа struct PostResponse: Decodable { let json: PostData } /// Декодируем ответ let decodedResponse = try JSONDecoder().decode(PostResponse.self, from: data) print("Имя: \(decodedResponse.json.name), Возраст: \(decodedResponse.json.age)")

5 Обработка ошибок

Лучше всего использовать enum с типизированными ошибками, чтобы сделать код надежнее:

/// Определяем ошибки сети enum NetworkingError: Error { case encodingFailed(innerError: EncodingError) case decodingFailed(innerError: DecodingError) case invalidStatusCode(statusCode: Int) case requestFailed(innerError: URLError) case otherError(innerError: Error) } /// Функция с обработкой ошибок func performPOSTRequest() async throws { do { let url = URL(string: "https://httpbin.org/post")! var request = URLRequest(url: url) request.httpMethod = "POST" request.setValue("application/json", forHTTPHeaderField: "Content-Type") let postData = PostData(name: "John", age: 30) let jsonData = try JSONEncoder().encode(postData) request.httpBody = jsonData let (data, response) = try await URLSession.shared.data(for: request) /// Проверяем код статуса guard let statusCode = (response as? HTTPURLResponse)?.statusCode, (200...299).contains(statusCode) else { throw NetworkingError.invalidStatusCode(statusCode: (response as? HTTPURLResponse)?.statusCode ?? -1) } /// Декодируем JSON let decodedResponse = try JSONDecoder().decode(PostResponse.self, from: data) print("Ответ: \(decodedResponse.json.name), \(decodedResponse.json.age)") } catch let error as DecodingError { throw NetworkingError.decodingFailed(innerError: error) } catch let error as EncodingError { throw NetworkingError.encodingFailed(innerError: error) } catch let error as URLError { throw NetworkingError.requestFailed(innerError: error) } catch { throw NetworkingError.otherError(innerError: error) } }

📌 Что мы сделали?

  • Проверяем код ответа (200-299).
  • Используем try/catch для обработки разных типов ошибок (кодирования, декодирования, сетевых сбоев).
  • Выбрасываем NetworkingError, чтобы вызывающий код мог корректно обработать ошибку.

Заключение

Использование async/await с URLSession делает работу с сетью проще и надежнее. Мы рассмотрели:

✅ GET-запросы с параметрами

✅ POST-запросы с JSON-данными

✅ Декодирование JSON-ответов

✅ Обработку ошибок

Теперь вы готовы писать чистый и понятный код для сетевых запросов в Swift! 🚀

Начать дискуссию