Введение: почему важно уметь работать с HTTP на Swift
Современные мобильные и десктопные приложения редко существуют изолированно. Практически любое приложение — от погодного виджета до банковского клиента — обменивается данными с сервером. Для этого используется протокол HTTP, а на стороне Swift (iOS/macOS) основным инструментом является фреймворк Foundation и его класс URLSession.
В этой статье мы разберём, как правильно создавать HTTP клиент на Swift: от простого GET-запроса до сложной обработки ошибок и работы с JSON. Материал будет полезен как новичкам, только знакомящимся с iOS-разработкой, так и опытным разработчикам, желающим систематизировать знания.
Мы рассмотрим современный подход с использованием async/await (Swift 5.5+), что делает код чистым и читаемым, а также затронем классические методы с замыканиями (completion handlers) для обратной совместимости.
Основы URLSession: создание и конфигурация
Класс URLSession — это главный «дирижёр» сетевых запросов в Swift. Он управляет соединениями, кэшированием и аутентификацией. Прежде чем делать запросы, нужно создать экземпляр сессии.
Типы сессий
- Shared (singleton):
URLSession.shared— простая сессия без кастомизации. Подходит для базовых запросов. - Default:
URLSession(configuration: .default)— позволяет настроить таймауты, кэш и заголовки. - Ephemeral:
URLSession(configuration: .ephemeral)— не сохраняет кэш и cookies на диск. Идеально для приватных данных. - Background:
URLSession(configuration: .background(withIdentifier:))— для фоновой загрузки/отправки данных, даже когда приложение свёрнуто.
import Foundation
// Простая сессия
let sharedSession = URLSession.shared
// Кастомная сессия с таймаутом 30 секунд
let config = URLSessionConfiguration.default
config.timeoutIntervalForRequest = 30
config.timeoutIntervalForResource = 60
let customSession = URLSession(configuration: config)
Создание и отправка HTTP запросов
Запрос формируется с помощью класса URLRequest. Он содержит URL, HTTP-метод, заголовки и тело запроса. Рассмотрим основные методы: GET и POST.
GET-запрос с async/await (современный подход)
Начиная с Swift 5.5, Apple добавила поддержку асинхронных функций (async/await). Это самый лаконичный и безопасный способ работы с сетью.
func fetchData(from urlString: String) async throws -> Data {
guard let url = URL(string: urlString) else {
throw URLError(.badURL)
}
var request = URLRequest(url: url)
request.httpMethod = "GET"
request.setValue("application/json", forHTTPHeaderField: "Accept")
let (data, response) = try await URLSession.shared.data(for: request)
// Проверяем HTTP статус
guard let httpResponse = response as? HTTPURLResponse,
(200...299).contains(httpResponse.statusCode) else {
throw URLError(.badServerResponse)
}
return data
}
// Использование:
Task {
do {
let data = try await fetchData(from: "https://api.example.com/users")
print("Получено \(data.count) байт")
} catch {
print("Ошибка: \(error.localizedDescription)")
}
}
POST-запрос с телом JSON
Для отправки данных на сервер используется метод POST. Тело запроса обычно кодируется в JSON.
struct User: Codable {
let name: String
let email: String
}
func createUser(_ user: User) async throws -> Data {
let url = URL(string: "https://api.example.com/users")!
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
request.setValue("application/json", forHTTPHeaderField: "Accept")
// Кодируем модель в JSON
request.httpBody = try JSONEncoder().encode(user)
let (data, response) = try await URLSession.shared.data(for: request)
guard let httpResponse = response as? HTTPURLResponse,
(200...299).contains(httpResponse.statusCode) else {
throw URLError(.badServerResponse)
}
return data
}
// Пример вызова:
let newUser = User(name: "Иван Петров", email: "ivan@example.com")
Task {
do {
let responseData = try await createUser(newUser)
print("Пользователь создан: \(String(data: responseData, encoding: .utf8) ?? "")")
} catch {
print("Ошибка создания: \(error)")
}
}
Работа с JSON: декодирование ответов
Современные API возвращают данные в формате JSON. Swift предоставляет мощный протокол Codable для автоматического парсинга. Создадим модель, соответствующую ответу сервера.
// Модель для ответа списка пользователей
struct UserResponse: Codable {
let id: Int
let name: String
let email: String
let isActive: Bool
enum CodingKeys: String, CodingKey {
case id, name, email
case isActive = "is_active" // Маппинг snake_case
}
}
func fetchUsers() async throws -> [UserResponse] {
let url = URL(string: "https://api.example.com/users")!
let (data, _) = try await URLSession.shared.data(from: url)
let decoder = JSONDecoder()
return try decoder.decode([UserResponse].self, from: data)
}
// Использование:
Task {
if let users = try? await fetchUsers() {
for user in users {
print("\(user.name): \(user.email)")
}
}
}
Обработка ошибок и best practices
Сеть — ненадёжная среда. Запросы могут упасть из-за отсутствия интернета, таймаута или ошибки сервера. Рассмотрим типовые сценарии.
Типы сетевых ошибок
- URLError.notConnectedToInternet — нет соединения.
- URLError.timedOut — превышен таймаут.
- URLError.cannotFindHost — DNS не разрешил домен.
- HTTP статусы 4xx/5xx — ошибки сервера или клиента.
Пример robust-функции с обработкой ошибок
enum NetworkError: