WebSocket на Swift: создаём real-time приложения для iOS и macOS

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

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

Начать курс

Введение: почему WebSocket важен для Swift-разработчика

В современной мобильной и настольной разработке пользователи привыкли к мгновенным обновлениям: лента новостей обновляется без перезагрузки, статус собеседника в чате меняется в реальном времени, а биржевые котировки приходят с задержкой в миллисекунды. Традиционный HTTP с его запрос-ответной моделью для таких задач неэффективен — он создаёт избыточную нагрузку на сервер и батарею устройства.

На помощь приходит протокол WebSocket. Он устанавливает постоянное двунаправленное соединение между клиентом (iOS/macOS приложением на Swift) и сервером. В этой статье мы разберём два основных способа работы с WebSocket в Swift: нативный URLSessionWebSocketTask (iOS 13+ / macOS 10.15+) и популярную стороннюю библиотеку Starscream. Вы узнаете, как создать real-time чат, настроить автоматическое переподключение и обрабатывать ошибки.

Основы WebSocket в Swift: URLSessionWebSocketTask

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

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

Для начала создадим экземпляр URLSession и зададим конфигурацию. Обратите внимание: для работы с WebSocket нужно использовать протокол wss:// (защищённое соединение) или ws:// (незащищённое, только для разработки).

import Foundation

class WebSocketManager: NSObject {
    private var webSocketTask: URLSessionWebSocketTask?
    private let session: URLSession
    
    override init() {
        let config = URLSessionConfiguration.default
        config.timeoutIntervalForRequest = 30
        config.waitsForConnectivity = true
        self.session = URLSession(configuration: config, delegate: nil, delegateQueue: OperationQueue())
        super.init()
    }
    
    func connect() {
        guard let url = URL(string: "wss://echo.websocket.org") else { return }
        webSocketTask = session.webSocketTask(with: url)
        webSocketTask?.resume()
        print("WebSocket соединение установлено")
        receiveMessage()
    }
}

Отправка и получение сообщений

WebSocket поддерживает два типа сообщений: строковые (String) и бинарные (Data). Рассмотрим отправку:

func sendMessage(_ text: String) {
    let message = URLSessionWebSocketTask.Message.string(text)
    webSocketTask?.send(message) { error in
        if let error = error {
            print("Ошибка отправки: \(error.localizedDescription)")
        } else {
            print("Сообщение отправлено: \(text)")
        }
    }
}

Для получения сообщений необходимо реализовать рекурсивный вызов метода receive:

private func receiveMessage() {
    webSocketTask?.receive { [weak self] result in
        switch result {
        case .success(let message):
            switch message {
            case .string(let text):
                print("Получено текстовое сообщение: \(text)")
            case .data(let data):
                print("Получены бинарные данные: \(data.count) байт")
            @unknown default:
                break
            }
            // Продолжаем слушать следующие сообщения
            self?.receiveMessage()
        case .failure(let error):
            print("Ошибка получения: \(error.localizedDescription)")
            // Здесь можно реализовать переподключение
        }
    }
}

Закрытие соединения и обработка ошибок

Корректное завершение работы с WebSocket — важная часть production-приложения:

func disconnect() {
    let closeCode = URLSessionWebSocketTask.CloseCode.normalClosure
    webSocketTask?.cancel(with: closeCode, reason: nil)
    print("Соединение закрыто")
}

// Обработка ошибок соединения
func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
    if let error = error {
        print("Соединение разорвано с ошибкой: \(error.localizedDescription)")
        // Автоматическое переподключение через 5 секунд
        DispatchQueue.main.asyncAfter(deadline: .now() + 5) { [weak self] in
            self?.connect()
        }
    }
}

Библиотека Starscream: гибкость и дополнительные возможности

Хотя нативный URLSessionWebSocketTask покрывает базовые сценарии, для сложных проектов часто используют библиотеку Starscream от Dalton Cherry. Она предоставляет удобный делегатный подход, встроенную поддержку пингов, сжатия и автоматического переподключения.

Установка и подключение

Добавьте Starscream через Swift Package Manager (SPM) или CocoaPods. Для SPM:

// В Xcode: File → Add Packages → введите URL
// https://github.com/daltoniam/Starscream.git

Пример реализации real-time чата с Starscream

import Starscream

class ChatManager: WebSocketDelegate {
    private var socket: WebSocket?
    var onMessageReceived: ((String) -> Void)?
    
    func connectToChatServer() {
        var request = URLRequest(url: URL(string: "wss://chat.example.com/ws")!)
        request.timeoutInterval = 5
        // Добавляем заголовки для авторизации
        request.setValue("Bearer your_token", forHTTPHeaderField: "Authorization")
        
        socket = WebSocket(request: request)
        socket?.delegate = self
        socket?.connect()
    }
    
    // MARK: - WebSocketDelegate
    func didReceive(event: WebSocketEvent, client: WebSocket) {
        switch event {
        case .connected(let headers):
            print("Подключено. Заголовки: \(headers)")
        case .disconnected(let reason, let code):
            print("Отключено: \(reason) (код: \(code))")
            // Автоматическое переподключение через 3 секунды
            DispatchQueue.main.asyncAfter(deadline: .now() + 3) { [weak self] in
                self?.socket?.connect()
            }
        case .text(let string):
            print("Получено сообщение: \(string)")
            onMessageReceived?(string)
        case .binary(let data):
            print("Получены данные: \(data)")
        case .pong(let data):
            print("Pong: \(data?.description ?? "")")
        case .ping(let data):
            print("Ping: \(data?.description ?? "")")
        case .error(let error):
            print("Ошибка: \(error?.localizedDescription ?? "неизвестная")")
        case .viabilityChanged(let isViable):
            print("Соединение доступно: \(isViable)")
        case .reconnectSuggested(let shouldReconnect):
            if shouldReconnect {
                print("Рекомендуется переподключение")
                socket?.connect()
            }
        case .cancelled:
            print("Соединение отменено")
        default:
            break
        }
    }
    
    func send(text: String) {
        socket?.write(string:                

Блоги

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