WebSocket на Swift: real-time приложения от А до Я

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

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

Начать курс

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

Современные мобильные и десктопные приложения всё чаще требуют мгновенного обмена данными. Будь то чат, биржевой терминал, онлайн-игра или система push-уведомлений — пользователи ожидают реакции в реальном времени. Протокол WebSocket стал стандартом для таких задач, а в экосистеме Swift (iOS/macOS) его поддержка реализована нативно через URLSessionWebSocketTask.

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

Основы WebSocket в Swift: URLSessionWebSocketTask

Начиная с iOS 13 и macOS 10.15, Apple предоставляет встроенный класс URLSessionWebSocketTask в составе Foundation. Это избавляет от необходимости подключать сторонние библиотеки (например, Starscream) для базовых сценариев.

Создание WebSocket соединения

Для установки соединения необходимо создать сессию URLSession и задать конфигурацию:

import Foundation

class WebSocketManager {
    private var webSocketTask: URLSessionWebSocketTask?
    private let session: URLSession
    
    init() {
        let config = URLSessionConfiguration.default
        config.timeoutIntervalForRequest = 30
        config.waitsForConnectivity = true
        session = URLSession(configuration: config, delegate: self, delegateQueue: OperationQueue())
    }
    
    func connect() {
        guard let url = URL(string: "wss://echo.websocket.org") else { return }
        webSocketTask = session.webSocketTask(with: url)
        webSocketTask?.resume()
        receiveMessage()
    }
}

Обратите внимание на waitsForConnectivity — это позволяет автоматически восстанавливать соединение при появлении сети.

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

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)")
        }
    }
}

func sendData(_ data: Data) {
    let message = URLSessionWebSocketTask.Message.data(data)
    webSocketTask?.send(message) { error in
        // обработка ошибки
    }
}

Для получения сообщений используется рекурсивный вызов 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)")
        }
    }
}

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

Архитектура real-time приложения на Swift

Для промышленного использования WebSocket необходимо продумать архитектуру. Рекомендуется использовать сервисный слой с протоколом и делегатом, чтобы отделить сетевую логику от UI.

Протокол WebSocket сервиса

protocol WebSocketServiceProtocol: AnyObject {
    var isConnected: Bool { get }
    func connect()
    func disconnect()
    func send(text: String)
    func send(data: Data)
}

protocol WebSocketServiceDelegate: AnyObject {
    func webSocketDidConnect()
    func webSocketDidDisconnect(error: Error?)
    func webSocketDidReceive(text: String)
    func webSocketDidReceive(data: Data)
}

Реализация с обработкой ошибок и переподключением

class WebSocketService: NSObject, WebSocketServiceProtocol {
    private var webSocketTask: URLSessionWebSocketTask?
    private var session: URLSession!
    private var reconnectAttempts = 0
    private let maxReconnectAttempts = 5
    weak var delegate: WebSocketServiceDelegate?
    
    private(set) var isConnected = false
    
    override init() {
        super.init()
        let config = URLSessionConfiguration.default
        config.timeoutIntervalForRequest = 30
        session = URLSession(configuration: config, delegate: self, delegateQueue: OperationQueue())
    }
    
    func connect() {
        guard let url = URL(string: "wss://your-server.com/ws") else { return }
        webSocketTask = session.webSocketTask(with: url)
        webSocketTask?.resume()
        listen()
    }
    
    func disconnect() {
        webSocketTask?.cancel(with: .normalClosure, reason: nil)
        isConnected = false
    }
    
    // ... методы send и listen (аналогично предыдущему примеру)
    
    private func reconnect() {
        guard reconnectAttempts < maxReconnectAttempts else {
            delegate?.webSocketDidDisconnect(error: nil)
            return
        }
        reconnectAttempts += 1
        let delay = Double(reconnectAttempts) * 2.0 // экспоненциальная задержка
        DispatchQueue.global().asyncAfter(deadline: .now() + delay) { [weak self] in
            self?.connect()
        }
    }
}

extension WebSocketService: URLSessionWebSocketDelegate {
    func urlSession(_ session: URLSession, webSocketTask: URLSessionWebSocketTask, didOpenWithProtocol protocol: String?) {
        isConnected = true
        reconnectAttempts = 0
        delegate?.webSocketDidConnect()
    }
    
    func urlSession(_ session: URLSession, webSocketTask: URLSessionWebSocketTask, didCloseWith closeCode: URLSessionWebSocketTask.CloseCode, reason: Data?) {
        isConnected = false
        delegate?.webSocketDidDisconnect(error: nil)
        reconnect()
    }
}

Практический пример: real-time чат на SwiftUI

Создадим простой чат, используя WebSocket и SwiftUI. ViewModel будет подписываться на обновления через Combine.

ViewModel для чата

import SwiftUI
import Combine

class ChatViewModel: ObservableObject {
    @Published var messages: [Message] = []
    @Published var inputText = ""
    private let webSocketService = WebSocketService()
    private var cancellables = Set<AnyCancellable>()
    
    struct Message: Identifiable {
        let id = UUID()
        let text: String
        let isIncoming: Bool
    }
    
    init() {
        webSocketService.delegate = self
        webSocketService.connect()
    }
    
    func sendMessage() {
        guard !inputText.isEmpty else { return }
        let message = Message(text: inputText, isIncoming: false)
        messages.append(message)
        webSocketService.send(text: inputText)
        inputText = ""
    }
}

extension ChatViewModel: WebSocketServiceDelegate {
    func webSocketDidReceive                

Блоги

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