WebSocket на Swift: продвинутые real-time приложения для iOS/macOS

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

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

Начать курс

WebSocket на Swift: создание real-time приложений продвинутого уровня

В современной мобильной разработке real-time взаимодействие стало не просто фишкой, а необходимостью. Финансовые котировки, онлайн-игры, мессенджеры, совместное редактирование документов — все это требует мгновенной передачи данных между клиентом и сервером. В экосистеме Apple фреймворки Swift и Foundation предоставляют мощные инструменты для работы с протоколом WebSocket. В этой статье мы рассмотрим продвинутые техники: от нативного URLSessionWebSocketTask до кастомной обработки ошибок, многопоточности и оптимизации производительности.

1. Нативная реализация WebSocket через URLSessionWebSocketTask (iOS 13+)

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

1.1. Создание и настройка сессии

Для начала создадим менеджер WebSocket с поддержкой повторного подключения и пулинга потоков:

import Foundation

class WebSocketManager: NSObject {
    private var webSocketTask: URLSessionWebSocketTask?
    private var session: URLSession!
    private let queue = DispatchQueue(label: "com.websocket.queue", qos: .userInitiated)
    private var isConnected = false
    
    override init() {
        super.init()
        let config = URLSessionConfiguration.default
        config.waitsForConnectivity = true
        config.timeoutIntervalForResource = 300
        session = URLSession(configuration: config, delegate: self, delegateQueue: nil)
    }
    
    func connect(url: URL) {
        webSocketTask = session.webSocketTask(with: url)
        webSocketTask?.resume()
        receiveMessage()
    }
    
    func disconnect() {
        webSocketTask?.cancel(with: .normalClosure, reason: nil)
        isConnected = false
    }
}

1.2. Продвинутая обработка сообщений с типизацией

Создадим enum для типов сообщений и реализуем парсинг:

enum WebSocketMessage {
    case text(String)
    case data(Data)
    case ping
    case pong
    case error(Error)
}

extension WebSocketManager: URLSessionWebSocketDelegate {
    func urlSession(_ session: URLSession, webSocketTask: URLSessionWebSocketTask, didOpenWithProtocol protocol: String?) {
        isConnected = true
        print("WebSocket connected")
    }
    
    func urlSession(_ session: URLSession, webSocketTask: URLSessionWebSocketTask, didCloseWith closeCode: URLSessionWebSocketTask.CloseCode, reason: Data?) {
        isConnected = false
        print("WebSocket closed: \(closeCode)")
        // Автоматическое переподключение через 5 секунд
        DispatchQueue.global().asyncAfter(deadline: .now() + 5) { [weak self] in
            self?.reconnect()
        }
    }
    
    private func receiveMessage() {
        webSocketTask?.receive { [weak self] result in
            switch result {
            case .success(let message):
                self?.handleMessage(message)
                self?.receiveMessage() // Циклический прием
            case .failure(let error):
                self?.handleError(error)
            }
        }
    }
    
    private func handleMessage(_ message: URLSessionWebSocketTask.Message) {
        switch message {
        case .string(let text):
            processText(text)
        case .data(let data):
            processData(data)
        @unknown default:
            break
        }
    }
}

2. Продвинутая работа с многопоточностью и производительностью

Real-time приложения требуют высокой производительности. Рассмотрим ключевые оптимизации.

2.1. Потокобезопасная очередь сообщений

class ThreadSafeMessageQueue {
    private var queue = [WebSocketMessage]()
    private let lock = NSLock()
    
    func enqueue(_ message: WebSocketMessage) {
        lock.lock()
        queue.append(message)
        lock.unlock()
    }
    
    func dequeueAll() -> [WebSocketMessage] {
        lock.lock()
        let messages = queue
        queue.removeAll()
        lock.unlock()
        return messages
    }
}

2.2. Использование Combine для реактивного стриминга

import Combine

class ReactiveWebSocket {
    private let subject = PassthroughSubject<WebSocketMessage, Never>()
    
    var publisher: AnyPublisher<WebSocketMessage, Never> {
        subject.eraseToAnyPublisher()
    }
    
    func send(message: WebSocketMessage) {
        subject.send(message)
    }
    
    // Фильтрация сообщений по типу
    func textPublisher() -> AnyPublisher<String, Never> {
        publisher
            .compactMap { message in
                if case .text(let text) = message { return text }
                return nil
            }
            .eraseToAnyPublisher()
    }
}

2.3. Оптимизация памяти: пулинг объектов и буферизация

class MessageBuffer {
    private var buffer = [Data]()
    private let maxSize = 100
    private var flushTimer: DispatchSourceTimer?
    
    func append(_ data: Data) {
        buffer.append(data)
        if buffer.count >= maxSize {
            flush()
        }
    }
    
    func flush() {
        guard !buffer.isEmpty else { return }
        let batch = buffer
        buffer.removeAll()
        // Отправка батча на сервер
        sendBatch(batch)
    }
    
    private func sendBatch(_ batch: [Data]) {
        // Сериализация и отправка
        let combined = batch.reduce(Data(), +)
        webSocketTask?.send(.data(combined)) { error in
            if let error = error { print("Batch send error: \(error)") }
        }
    }
}

3. Обработка ошибок и восстановление соединения

В реальных приложениях стабильность критична. Реализуем механизм exponential backoff для повторных подключений.

3.1. Exponential backoff с джиттером

class ReconnectionHandler {
    private var attempt = 0
    private let maxAttempts = 10
    private let baseDelay: TimeInterval = 1.0
    
    func getNextDelay() -> TimeInterval {
        attempt += 1
        if attempt > maxAttempts {
            return -1 // Сигнал о прекращении попыток
        }
        let exponential = baseDelay * pow(2.0, Double(attempt - 1))
        let jitter = Double.random(in: 0...0.3 * exponential)
        return exponential + jitter
    }
    
    func reset() {
        attempt = 0
    }
}

3.2. Graceful shutdown и сохранение состояния

class StatefulWebSocket {
    private var pendingMessages = [Data]()
    private let stateLock = NSLock()
    
    func sendWithGuarantee(_ data: Data) {
        stateLock.lock()
        if isConnected {
            webSocketTask?.send(.data(data)) { error in
                if error != nil {
                    self.pendingMessages.append(data)
                }
            }
        } else {
            pendingMessages.append(data)
        }
        stateLock.unlock()
    }
    
    func flushPendingOnReconnect() {
        stateLock.lock()
        let messages = pendingMessages                

Блоги

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