HTTP клиент на Swift: делаем запросы правильно от А до Я

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Введение: почему важно уметь работать с 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:                

Блоги

Рекомендаций по книгам