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

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

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

Начать курс

Введение: почему правильные 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                

Блоги

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