← Назад к вопросам

Расскажи про URLSession

2.0 Middle🔥 231 комментариев
#Работа с сетью

Комментарии (1)

🐱
deepseek-v3.2PrepBro AI6 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Что такое URLSession?

URLSession — это фундаментальный API в iOS/macOS фреймворке Foundation, предназначенный для выполнения сетевых операций: загрузки данных, загрузки и выгрузки файлов, а также взаимодействия с веб-сокетами по протоколам HTTP/HTTPS. Он представляет собой мощную и гибкую абстракцию над сетевыми запросами, пришедшую на смену устаревшему NSURLConnection в iOS 7 и macOS 10.9. Его основная задача — управлять группами (сессиями) связанных сетевых задач, предоставляя тонкий контроль над кешированием, аутентификацией, управлением cookies и поведением в фоновом режиме.

Ключевые преимущества URLSession:

  • Гибкость конфигурации: Разные типы сессий (default, ephemeral, background) для различных сценариев.
  • Асинхронность по умолчанию: Все задачи выполняются асинхронно, не блокируя главный поток.
  • Поддержка фоновых загрузок: Продолжение загрузки данных даже когда приложение свернуто или находится в фоне.
  • Делегирование: Полный контроль над процессом запроса и ответа через методы делегата.
  • Интеграция с современными API: Полная поддержка async/await (начиная с iOS 15), Combine (с iOS 13), и классических completion handlers.

Архитектура и основные компоненты

Архитектура строится вокруг трех основных сущностей:

  1. URLSession (Сама сессия): Координирует группу связанных сетевых задач. Создается с помощью URLSessionConfiguration, которая определяет поведение всех задач в этой сессии (таймауты, политики кеширования, дополнительные HTTP-заголовки и т.д.).

  2. URLSessionTask (Задача): Абстрактный класс, представляющий единицу работы (один сетевой запрос). Существуют конкретные типы задач для разных целей:

    *   **URLSessionDataTask**: Для загрузки данных в память (например, JSON или небольшого изображения). Самый часто используемый тип.
    *   **URLSessionDownloadTask**: Для загрузки файлов непосредственно на диск с поддержкой возобновления при обрыве соединения.
    *   **URLSessionUploadTask**: Для загрузки (upload) данных (например, файлов) на сервер.
    *   **URLSessionStreamTask** (редко используется): Для установки TCP/IP-соединений.
    *   **URLSessionWebSocketTask** (iOS 13+): Для работы с WebSocket.

  1. URLSessionDelegate и его специализации: Набор протоколов, которые позволяют отслеживать и реагировать на события в жизненном цикле запроса: получение данных по частям, редиректы, проблемы с аутентификацией, завершение загрузки файла.

Типы конфигураций сессии (URLSessionConfiguration)

// 1. Конфигурация по умолчанию. Использует глобальный кеш и хранилище кук.
let defaultConfig = URLSessionConfiguration.default

// 2. Эфемерная конфигурация. Не сохраняет данные на диск (кеш, куки).
// Идеально для приватных сессий, например, в режиме инкогнито.
let ephemeralConfig = URLSessionConfiguration.ephemeral

// 3. Фоновая конфигурация. Позволяет выполнять загрузки/выгрузки,
// даже когда приложение не активно. Идентификатор должен быть уникальным.
let backgroundConfig = URLSessionConfiguration.background(
    withIdentifier: "com.yourapp.backgroundDownload"
)
// Настройка конфигурации
defaultConfig.timeoutIntervalForRequest = 30.0
defaultConfig.httpAdditionalHeaders = ["User-Agent": "MyApp/1.0"]
defaultConfig.waitsForConnectivity = true // Ждать появления сети, если она недоступна

Практическое использование

1. Простой запрос с Completion Handler (классический способ)

guard let url = URL(string: "https://api.example.com/data") else { return }

// Создаем сессию с конфигурацией по умолчанию
let session = URLSession.shared // Используем общую (синглтон) сессию для простых запросов

let task = session.dataTask(with: url) { data, response, error in
    // Этот блок выполняется в фоновом потоке!
    
    // 1. Проверяем на ошибку сети
    if let error = error {
        print("Network error: \(error)")
        return
    }
    
    // 2. Проверяем HTTP-статус код
    guard let httpResponse = response as? HTTPURLResponse,
          (200...299).contains(httpResponse.statusCode) else {
        print("Server returned an error")
        return
    }
    
    // 3. Обрабатываем полученные данные
    guard let data = data else { return }
    
    do {
        let decodedObject = try JSONDecoder().decode(MyModel.self, from: data)
        // Для обновления UI обязательно переходим на главный поток
        DispatchQueue.main.async {
            self.updateUI(with: decodedObject)
        }
    } catch {
        print("Decoding error: \(error)")
    }
}
// Запускаем задачу (без этого запрос не выполнится!)
task.resume()

2. Запрос с использованием современного async/await (iOS 15+)

func fetchData() async {
    guard let url = URL(string: "https://api.example.com/data") else { return }
    
    // Используем общую сессию
    let session = URLSession.shared
    
    do {
        // Вызов приостанавливает текущую таску, но не блокирует поток
        let (data, response) = try await session.data(from: url)
        
        guard let httpResponse = response as? HTTPURLResponse,
              httpResponse.statusCode == 200 else {
            throw URLError(.badServerResponse)
        }
        
        let decodedObject = try JSONDecoder().decode(MyModel.self, from: data)
        // Так как async-функции могут быть вызваны с MainActor, проверяем контекст
        await MainActor.run {
            self.updateUI(with: decodedObject)
        }
        
    } catch {
        print("Request failed: \(error)")
    }
}

3. Использование делегатов для отслеживания прогресса загрузки

class DownloadManager: NSObject, URLSessionDownloadDelegate {
    lazy var session: URLSession = {
        let config = URLSessionConfiguration.default
        return URLSession(configuration: config, delegate: self, delegateQueue: nil)
    }()
    
    func startDownload(from urlString: String) {
        guard let url = URL(string: urlString) else { return }
        let task = session.downloadTask(with: url)
        task.resume()
    }
    
    // Делегатный метод: вызывается при завершении загрузки
    func urlSession(_ session: URLSession, 
                    downloadTask: URLSessionDownloadTask, 
                    didFinishDownloadingTo location: URL) {
        // Временный файл находится по адресу `location`
        // Мы должны переместить его в постоянное хранилище (например, в папку Documents)
        let fileManager = FileManager.default
        let docsPath = fileManager.urls(for: .documentDirectory, in: .userDomainMask)[0]
        let permanentPath = docsPath.appendingPathComponent("downloadedFile.zip")
        
        do {
            try fileManager.moveItem(at: location, to: permanentPath)
            print("File saved to: \(permanentPath)")
        } catch {
            print("Error saving file: \(error)")
        }
    }
    
    // Делегатный метод: вызывается для отчета о прогрессе
    func urlSession(_ session: URLSession, 
                    downloadTask: URLSessionDownloadTask, 
                    didWriteData bytesWritten: Int64, 
                    totalBytesWritten: Int64, 
                    totalBytesExpectedToWrite: Int64) {
        let progress = Float(totalBytesWritten) / Float(totalBytesExpectedToWrite)
        DispatchQueue.main.async {
            self.progressView.progress = progress
        }
    }
}

Важные аспекты и лучшие практики

  • Управление памятью: Ссылка на сессию (URLSession) должна жить дольше, чем ее задачи. Если вы создаете сессию с делегатом, храните сильную ссылку на нее (например, в свойстве класса), иначе делегатные методы перестанут вызываться.
  • Отмена и приостановка: Задачи можно отменить (task.cancel()) или приостановить (task.suspend()). Отмена может быть как "мягкой" (позволяет завершиться completion handler), так и "жесткой" (task.cancel(byProducingResumeData:) для URLSessionDownloadTask).
  • Таймауты: Настраиваются на уровне конфигурации сессии (timeoutIntervalForRequest - для одного пакета данных, timeoutIntervalForResource - для всей задачи).
  • Кеширование: Политика кеширования (requestCachePolicy) определяет, когда использовать кешированные данные. Работает совместно с URLCache, который можно настраивать под свои нужды.
  • Безопасность (SSL/TLS, App Transport Security): URLSession автоматически применяет современные стандарты безопасности. Для работы с небезопасными HTTP-соединениями или кастомными SSL-сертификатами требуется настройка ATS в Info.plist и реализация соответствующих делегатных методов (urlSession(_:didReceive:completionHandler:)).

Итог: URLSession — это не просто "менеджер запросов", а комплексная система для сетевого взаимодействия. Его правильное использование, с учетом типа задачи (простой запрос, большая загрузка, фоновая операция) и выбора подходящего API (completionHandler, async/await, Combine, делегаты), является ключевым навыком для современного iOS-разработчика.