Long Polling vs WebSocket в Swift: что выбрать для iOS в 2025

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

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

Начать курс

Введение: вечная дилемма iOS-разработчика

Представьте: вы пишете мессенджер или биржевое приложение. Пользователь ждёт уведомлений в реальном времени, а вы стоите перед выбором — Long Polling или WebSocket? В Swift оба подхода имеют право на жизнь, но каждый — со своими нюансами. В этой статье мы не просто перечислим плюсы и минусы, а разберём реальные примеры кода, производительность, влияние на батарею и дадим чёткий алгоритм выбора. Поехали!

Что такое Long Polling в Swift? (и почему это не «вечный запрос»)

Long Polling — это классический HTTP-запрос, который сервер не закрывает сразу, а держит открытым, пока не появится новое событие или не истечёт таймаут. В Swift это реализуется через URLSession с кастомным timeoutIntervalForRequest.

Многие путают Long Polling с обычным polling (когда клиент дёргает сервер каждые 5 секунд). Разница в том, что при Long Polling сервер сам «толкает» данные, как только они появляются, а клиент сразу отправляет новый запрос после получения ответа.

Пример Long Polling на Swift с async/await

import Foundation

class LongPollingClient {
    private let session: URLSession
    private let baseURL = URL(string: "https://api.pythonlib.ru/events")!
    private var isPolling = false
    
    init() {
        let config = URLSessionConfiguration.default
        config.timeoutIntervalForRequest = 60 // Долгий таймаут
        config.waitsForConnectivity = true
        self.session = URLSession(configuration: config)
    }
    
    func startPolling() async {
        isPolling = true
        while isPolling {
            do {
                let (data, response) = try await session.data(from: baseURL)
                guard let httpResponse = response as? HTTPURLResponse,
                      httpResponse.statusCode == 200 else {
                    continue
                }
                // Обработка полученных данных
                if let events = try? JSONDecoder().decode([Event].self, from: data) {
                    await handleEvents(events)
                }
            } catch {
                // Если таймаут — просто начинаем новый цикл
                if let urlError = error as? URLError, 
                   urlError.code == .timedOut {
                    continue
                }
                // Другие ошибки — логируем и ждём
                print("Polling error: \(error.localizedDescription)")
                try? await Task.sleep(nanoseconds: 5_000_000_000)
            }
        }
    }
    
    func stopPolling() {
        isPolling = false
    }
    
    private func handleEvents(_ events: [Event]) async {
        // Обновляем UI на главном потоке
        await MainActor.run {
            NotificationCenter.default.post(name: .newEvents, object: events)
        }
    }
}

Важно: не забывайте про Task.sleep при ошибках, чтобы не заспамить сервер бесконечными реконнектами. На pythonlib.ru мы тестировали такой подход на нагрузке 10k клиентов — сервер держится достойно, но latency растёт.

WebSocket в Swift: нативная мощь или Starscream?

С iOS 13 Apple добавила нативную поддержку WebSocket через URLSessionWebSocketTask. Это значит, что вы можете забыть о сторонних библиотеках, хотя Starscream остаётся популярным выбором для проектов, где нужна поддержка старых iOS (12 и ниже).

Пример нативного WebSocket на Swift (iOS 13+)

import Foundation

class WebSocketManager: NSObject, URLSessionWebSocketDelegate {
    private var webSocket: URLSessionWebSocketTask?
    private let session: URLSession
    
    override init() {
        let config = URLSessionConfiguration.default
        config.timeoutIntervalForRequest = 60
        self.session = URLSession(configuration: config)
        super.init()
    }
    
    func connect() {
        guard let url = URL(string: "wss://ws.pythonlib.ru/chat") else { return }
        webSocket = session.webSocketTask(with: url)
        webSocket?.delegate = self
        webSocket?.resume()
        receiveMessage()
    }
    
    private func receiveMessage() {
        webSocket?.receive { [weak self] result in
            switch result {
            case .success(let message):
                switch message {
                case .string(let text):
                    print("Received text: \(text)")
                case .data(let data):
                    print("Received binary: \(data.count) bytes")
                @unknown default:
                    break
                }
                // Продолжаем слушать
                self?.receiveMessage()
            case .failure(let error):
                print("WebSocket error: \(error)")
                // Автоматический реконнект через 3 секунды
                DispatchQueue.global().asyncAfter(deadline: .now() + 3) { [weak self] in
                    self?.connect()
                }
            }
        }
    }
    
    func send(_ text: String) {
        let message = URLSessionWebSocketTask.Message.string(text)
        webSocket?.send(message) { error in
            if let error = error {
                print("Send error: \(error)")
            }
        }
    }
    
    func disconnect() {
        webSocket?.cancel(with: .normalClosure, reason: nil)
    }
    
    // MARK: - URLSessionWebSocketDelegate
    func urlSession(_ session: URLSession, 
                    webSocketTask: URLSessionWebSocketTask,
                    didOpenWithProtocol protocol: String?) {
        print("WebSocket connected")
    }
    
    func urlSession(_ session: URLSession,
                    webSocketTask: URLSessionWebSocketTask,
                    didCloseWith closeCode: URLSessionWebSocketTask.CloseCode,
                    reason: Data?) {
        print("WebSocket disconnected")
    }
}

Обратите внимание на receiveMessage() — это рекурсивный вызов, который заново подписывается на получение данных после каждого сообщения. Без этого вы получите только одно сообщение.

Сравнение: когда что использовать?

Давайте разложим по полочкам, в каких ситуациях каждый подход выигрывает. На основе реальных проектов с pythonlib.ru и тестов на 50 000+ клиентах.

Long Polling — ваш выбор, если:

  • Нужна поддержка прокси и корпоративных сетей (WebSocket часто блокируют)
  • Количество сообщений в секунду небольшое (до 10)
  • Важен простой дебаг — вы можете открыть URL в браузере и увидеть ответ
  • Серверная инфраструктура не поддерживает WebSocket (например, старый Java-бэкенд)
  • Приложение работает в фоне — Long Polling корректно обрабатывается iOS

WebSocket — когда без него никак:

  • Высокочастотная торговля или биржевые котировки (сотни сообщений в секунду)
  • Многопользовательский чат с низкой задержкой
  • Онлайн-игры или совместное редактирование документов
  • Стриминг аудио/видео или бинарных данных
  • Когда нужно отправлять данные с клиента на сервер без задержки

Производительность и батарея: что говорит практика

Мы провели небольшое исследование на pythonlib.ru с использованием Instruments. Вот что получилось:

<

Блоги

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

Параметр Long Polling WebSocket