Настройка URLSession для сетевых запросов в iOS-приложении

TRUETECH занимается разработкой, поддержкой и обслуживанием мобильных приложений iOS, Android, PWA. Имеем большой опыт и экспертизу для публикации мобильных приложений в популярные маркеты Google Play, App Store, Amazon, AppGallery и другие.
Разработка и поддержка любых видов мобильных приложений:
Информационные и развлекательные мобильные приложения
Новостные приложения, игры, справочники, онлайн-каталоги, погодные, фитнес и здоровье, туристические, образовательные, социальные сети и мессенджеры, квиз, блоги и подкасты, форумы, агрегаторы
Мобильные приложения электронной коммерции
Интернет-магазины, B2B-приложения, маркетплейсы, онлайн-обменники, кэшбэк-сервисы, биржи, дропшиппинг-платформы, программы лояльности, доставка еды и товаров, платежные системы
Мобильные приложения для управления бизнес-процессами
CRM-системы, ERP-системы, управление проектами, инструменты для команды продаж, учет финансов, управление производством, логистика и доставка, управление персоналом, системы мониторинга данных
Мобильные приложения электронных услуг
Доски объявлений, онлайн-школы, онлайн-кинотеатры, платформы предоставления электронных услуг, платформы кешбека, видеохостинги, тематические порталы, платформы онлайн-бронирования и записи, платформы онлайн-торговли

Это лишь некоторые из типы мобильных приложений, с которыми мы работаем, и каждый из них может иметь свои специфические особенности и функциональность, а также быть адаптированным под конкретные потребности и цели клиента.

Предлагаемые услуги
Показано 1 из 1 услугВсе 1735 услуг
Настройка URLSession для сетевых запросов в iOS-приложении
Средняя
от 1 рабочего дня до 3 рабочих дней
Часто задаваемые вопросы
Наши компетенции:
Этапы разработки
Последние работы
  • image_mobile-applications_feedme_467_0.webp
    Разработка мобильного приложения для компании FEEDME
    756
  • image_mobile-applications_xoomer_471_0.webp
    Разработка мобильного приложения для компании XOOMER
    624
  • image_mobile-applications_rhl_428_0.webp
    Разработка мобильного приложения для компании RHL
    1052
  • image_mobile-applications_zippy_411_0.webp
    Разработка мобильного приложения для компании ZIPPY
    947
  • image_mobile-applications_affhome_429_0.webp
    Разработка мобильного приложения для компании Affhome
    862
  • image_mobile-applications_flavors_409_0.webp
    Разработка мобильного приложения для компании FLAVORS
    445

Настройка URLSession для сетевых запросов в iOS-приложении

URLSession — стандартный сетевой стек Apple, и большинство проблем здесь возникает не от незнания API, а от неправильной конфигурации: дефолтный URLSession.shared работает в мелких примерах, но в продакшене приводит к утечкам памяти, нарушению политики App Transport Security и сложно диагностируемым таймаутам.

Где чаще всего ошибаются

Неправильный URLSessionConfiguration. .shared не поддерживает background-transfer и не даёт настроить таймауты на уровне сессии. Для API-клиента нужно минимум:

let config = URLSessionConfiguration.default
config.timeoutIntervalForRequest = 15
config.timeoutIntervalForResource = 60
config.requestCachePolicy = .reloadIgnoringLocalCacheData
config.urlCache = nil
let session = URLSession(configuration: config, delegate: self, delegateQueue: nil)

delegateQueue: nil означает, что URLSession создаст свою serial-очередь. Если передать OperationQueue.main — все completion handlers будут выполняться на main thread, что заблокирует UI при медленном разборе JSON.

Игнорирование URLSessionTaskDelegate при работе с SSL pinning. Без делегата невозможно переопределить urlSession(_:didReceive:completionHandler:) для проверки сертификата. Сколько приложений я видел, где SSL-pinning «реализован» через стороннюю библиотеку, но фактически не работает, потому что сессия создана без делегата — не счесть.

Утечки через [weak self]. URLSessionDataTask удерживает strong-ссылку на делегат сессии до явного вызова session.invalidateAndCancel() или finishTasksAndInvalidate(). Если URLSession хранится как свойство класса, а сам класс при deinit не инвалидирует сессию — цикл сохраняется.

Как мы строим сетевой слой

Основа — Protocol-Oriented подход с NetworkClient протоколом, что позволяет мокировать запросы в Unit-тестах без необходимости поднимать сервер.

protocol NetworkClient {
    func send<T: Decodable>(_ request: URLRequest) async throws -> T
}

final class URLSessionNetworkClient: NetworkClient {
    private let session: URLSession
    private let decoder: JSONDecoder

    init(session: URLSession = .init(configuration: .default)) {
        self.session = session
        self.decoder = JSONDecoder()
        self.decoder.keyDecodingStrategy = .convertFromSnakeCase
        self.decoder.dateDecodingStrategy = .iso8601
    }

    func send<T: Decodable>(_ request: URLRequest) async throws -> T {
        let (data, response) = try await session.data(for: request)
        guard let http = response as? HTTPURLResponse else {
            throw NetworkError.invalidResponse
        }
        guard (200..<300).contains(http.statusCode) else {
            throw NetworkError.httpError(statusCode: http.statusCode, data: data)
        }
        return try decoder.decode(T.self, from: data)
    }
}

Async/await вместо completion handlers — это не просто синтаксический сахар. Structured concurrency позволяет отменять запросы через Task.cancel(), который автоматически вызывает task.cancel() на уровне URLSession. Старая схема с completion-блоками не давала такого контроля.

Retry-логика реализуется через обёртку, а не засорение основного клиента:

func sendWithRetry<T: Decodable>(
    _ request: URLRequest,
    maxAttempts: Int = 3,
    delay: Duration = .seconds(1)
) async throws -> T {
    var lastError: Error
    for attempt in 0..<maxAttempts {
        do {
            return try await send(request)
        } catch NetworkError.httpError(let code, _) where code >= 500 {
            lastError = NetworkError.httpError(statusCode: code, data: nil)
            if attempt < maxAttempts - 1 {
                try await Task.sleep(for: delay * Double(attempt + 1))
            }
        } catch {
            throw error // не ретраим 4xx и ошибки декодирования
        }
    }
    throw lastError
}

Background Downloads. Для загрузки файлов — URLSessionConfiguration.background(withIdentifier:). Система может завершить процесс и возобновить загрузку при следующем запуске. Обязательный метод в AppDelegate:

func application(_ application: UIApplication,
                 handleEventsForBackgroundURLSession identifier: String,
                 completionHandler: @escaping () -> Void) {
    BackgroundDownloadManager.shared.completionHandler = completionHandler
}

Без этого обработчика iOS не перезапустит приложение после завершения фоновой загрузки.

Диагностика проблем

Инструменты: Charles Proxy или Proxyman — для инспекции трафика; Network Instrument в Xcode — для анализа количества параллельных соединений и поиска connection starvation; os_log с категорией com.apple.network — для низкоуровневого логирования Network.framework.

При ошибке NSURLErrorDomain -1001 (request timed out) первым делом смотреть на timeoutIntervalForRequest — по умолчанию 60 секунд, что неочевидно много для мобильного приложения. При NSURLErrorDomain -1200 (SSL error) — проверить ATS-политику в Info.plist и корректность цепочки сертификатов на сервере через openssl s_client.

Процесс работы

Аудит существующего сетевого слоя: конфигурация сессии, обработка ошибок, таймауты, работа с токенами авторизации.

Проектирование: API-клиент с поддержкой авторизации через URLSessionTaskDelegate или RequestInterceptor, обработка 401 с обновлением токена.

Разработка: реализация, покрытие Unit-тестами через mock-сессию (URLProtocol subclass).

Тестирование: интеграционные тесты на реальном API, проверка поведения при нестабильной сети через Network Link Conditioner.

Ориентиры по срокам

Задача Срок
Базовый API-клиент с async/await 1 день
+ SSL pinning + retry + token refresh 2–3 дня
Миграция существующего сетевого слоя 2–3 дня