Введение: почему правильные HTTP-запросы — основа любого приложения
В современной мобильной и десктопной разработке на Swift редко встречается приложение, которое работает полностью автономно. Практически любое iOS или macOS приложение взаимодействует с удалёнными серверами: загружает данные, отправляет формы, синхронизирует контент. HTTP-клиент — это ключевой компонент, от качества которого зависит не только скорость работы, но и безопасность, и стабильность всего приложения.
В этой статье мы разберём, как создать надёжный HTTP-клиент на Swift с использованием встроенного фреймворка Foundation. Вы узнаете, как правильно настраивать URLSession, обрабатывать ошибки, работать с JSON и использовать современные подходы, такие как async/await. Материал будет полезен как новичкам, только осваивающим Swift, так и опытным разработчикам, желающим систематизировать знания.
1. Основы URLSession: фундамент сетевого взаимодействия
В Swift основным инструментом для работы с HTTP является класс URLSession. Он входит в фреймворк Foundation и предоставляет гибкий API для отправки запросов и получения ответов. URLSession поддерживает несколько конфигураций, что позволяет адаптировать поведение под конкретные задачи.
Создание и настройка сессии
Первый шаг — создать экземпляр URLSession с определённой конфигурацией. Существует три типа конфигураций:
.default— стандартная конфигурация с кэшированием и сохранением куки;.ephemeral— без сохранения данных на диск (куки, кэш хранятся только в памяти);.background— для фоновых задач, которые могут выполняться даже после закрытия приложения.
Пример создания сессии с кастомной конфигурацией:
let configuration = URLSessionConfiguration.default
configuration.timeoutIntervalForRequest = 30
configuration.waitsForConnectivity = true
let session = URLSession(configuration: configuration)
Обратите внимание на параметр waitsForConnectivity: если он установлен в true, запрос не упадёт с ошибкой при отсутствии сети, а будет ждать восстановления соединения. Это особенно важно для мобильных приложений.
Базовый GET-запрос
Самый простой способ выполнить запрос — использовать метод dataTask(with:). Вот минимальный пример:
guard let url = URL(string: "https://api.example.com/users") else { return }
let task = session.dataTask(with: url) { data, response, error in
if let error = error {
print("Ошибка запроса: \(error.localizedDescription)")
return
}
guard let httpResponse = response as? HTTPURLResponse,
(200...299).contains(httpResponse.statusCode) else {
print("Некорректный ответ сервера")
return
}
guard let data = data else {
print("Нет данных в ответе")
return
}
// Обработка данных
print("Получено \(data.count) байт")
}
task.resume()
Важный момент: не забывайте вызывать resume(), иначе запрос не начнётся. Это одна из самых частых ошибок новичков.
2. Работа с JSON: от данных к объектам
Большинство современных API возвращают данные в формате JSON. В Swift для работы с JSON используется протокол Codable, который объединяет Encodable и Decodable. Это позволяет легко преобразовывать данные в модели и обратно.
Создание модели и декодирование
Предположим, сервер возвращает список пользователей в таком формате:
[
{
"id": 1,
"name": "Иван Петров",
"email": "ivan@example.com"
}
]
Создадим соответствующую структуру:
struct User: Codable {
let id: Int
let name: String
let email: String
}
Теперь добавим декодирование в обработчик запроса:
do {
let users = try JSONDecoder().decode([User].self, from: data)
print("Получено пользователей: \(users.count)")
} catch {
print("Ошибка декодирования JSON: \(error)")
}
Отправка POST-запроса с JSON
Для отправки данных на сервер нужно создать URLRequest и настроить его:
guard let url = URL(string: "https://api.example.com/users") else { return }
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
let newUser = User(id: 0, name: "Анна Смирнова", email: "anna@example.com")
do {
request.httpBody = try JSONEncoder().encode(newUser)
} catch {
print("Ошибка кодирования JSON: \(error)")
return
}
let task = session.dataTask(with: request) { data, response, error in
// Обработка ответа
}
task.resume()
Обратите внимание: Content-Type должен быть явно указан, иначе сервер может не распознать формат данных.
3. Современный подход: async/await и Combine
Начиная с iOS 13 и macOS 10.15, Apple представила новые способы асинхронного программирования. async/await (с iOS 15) делает код более читаемым и линейным, избавляя от «адских колбэков».
Асинхронный HTTP-клиент с async/await
Современная версия GET-запроса выглядит так:
func fetchUsers() async throws -> [User] {
guard let url = URL(string: "https://api.example.com/users") else {
throw URLError(.badURL)
}
let (data, response) = try await URLSession.shared.data(from: url)
guard let httpResponse = response as? HTTPURLResponse,
(200...299).contains(httpResponse.statusCode) else {
throw URLError(.badServerResponse)
}
return try JSONDecoder().decode([User].self, from: data)
}
Вызов такой функции прост и интуитивен:
Task {
do {
let users = try await fetchUsers()
print("Успешно загружено \(users.count) пользователей")
} catch {
print("Ошибка: \(error)")
}
}
Обработка ошибок и повторные попытки
В реальных приложениях сеть может быть нестабильной. Полезно добавить логику повторных попыток. Вот пример простого ретрая:
func fetchWithRetry(maxRetries: Int