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

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

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

Начать курс

Введение в WebSocket на Swift: от теории к практике

Современные мобильные и десктопные приложения всё чаще требуют мгновенной передачи данных. Будь то чат, биржевой терминал, онлайн-игра или система мониторинга — пользователи ожидают, что информация будет обновляться в реальном времени без постоянного обновления экрана. Именно здесь на помощь приходит протокол WebSocket.

В отличие от классического HTTP-запроса, который работает по принципу «запрос-ответ», WebSocket устанавливает постоянное двунаправленное соединение между клиентом и сервером. Это означает, что сервер может отправлять данные клиенту в любой момент без предварительного запроса. В экосистеме Apple для работы с WebSocket доступны как нативные решения (URLSessionWebSocketTask), так и популярные сторонние библиотеки, например Starscream.

В этой статье мы разберём, как интегрировать WebSocket в ваше Swift-приложение для iOS и macOS, напишем рабочие примеры кода и рассмотрим лучшие практики.

1. Нативная реализация WebSocket через URLSession

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

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

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

import Foundation

class WebSocketManager: NSObject {
    private var webSocketTask: URLSessionWebSocketTask?
    private let session: URLSession
    
    override init() {
        let config = URLSessionConfiguration.default
        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()
        receiveMessage()
    }
    
    func disconnect() {
        webSocketTask?.cancel(with: .normalClosure, reason: nil)
    }
}

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

WebSocket поддерживает отправку как текстовых (URLSessionWebSocketTask.Message.string), так и бинарных данных (.data). Для получения сообщений необходимо рекурсивно вызывать метод receive, так как он обрабатывает только одно сообщение за раз.

extension WebSocketManager {
    func sendMessage(_ text: String) {
        let message = URLSessionWebSocketTask.Message.string(text)
        webSocketTask?.send(message) { error in
            if let error = error {
                print("Ошибка отправки: \(error.localizedDescription)")
            }
        }
    }
    
    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:
                    print("Неизвестный тип сообщения")
                }
                // Рекурсивно продолжаем слушать
                self?.receiveMessage()
            case .failure(let error):
                print("Ошибка получения: \(error.localizedDescription)")
            }
        }
    }
}

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

2. Работа с библиотекой Starscream

Хотя нативный API покрывает большинство задач, библиотека Starscream остаётся популярным выбором благодаря удобному делегатному подходу и дополнительным возможностям, таким как автоматическое переподключение и поддержка старых версий iOS.

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

Starscream можно установить через Swift Package Manager (SPM) или CocoaPods. Для SPM добавьте пакет в Xcode: File → Add Packages → https://github.com/daltoniam/Starscream.git.

import Starscream

class StarscreamManager: WebSocketDelegate {
    var socket: WebSocket!
    
    func setupConnection() {
        var request = URLRequest(url: URL(string: "wss://echo.websocket.org")!)
        request.timeoutInterval = 5
        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)")
        case .text(let string):
            print("Текст: \(string)")
        case .binary(let data):
            print("Бинарные данные: \(data)")
        case .pong(let data):
            print("Pong: \(data?.description ?? "nil")")
        case .ping(let data):
            print("Ping: \(data?.description ?? "nil")")
        case .error(let error):
            print("Ошибка: \(error?.localizedDescription ?? "nil")")
        case .viabilityChanged(let isViable):
            print("Соединение активно: \(isViable)")
        case .reconnectSuggested(let shouldReconnect):
            print("Рекомендуется переподключение: \(shouldReconnect)")
        case .cancelled:
            print("Соединение отменено")
        default:
            break
        }
    }
}

Ключевое преимущество Starscream — единый метод didReceive(event:client:), который обрабатывает все возможные события: подключение, отключение, получение данных, ошибки и даже пинг-понг сообщения.

3. Создание real-time чата на SwiftUI

Давайте соберём всё вместе и создадим простое приложение-чат с использованием SwiftUI и нативного WebSocket. Это поможет понять, как интегрировать WebSocket в реальный проект.

Модель данных и ViewModel

import SwiftUI

struct Message: Identifiable {
    let id = UUID()
    let text: String
    let isIncoming: Bool
}

class ChatViewModel: ObservableObject {
    @Published var messages: [Message] = []
    @Published var isConnected = false
    
    private var webSocketTask: URLSessionWebSocketTask?
    private let session = URLSession.shared
    
    func connect() {
        guard let url = URL(string: "wss://echo.websocket.org") else { return }
        webSocketTask = session.webSocketTask(with: url)
        webSocketTask?.resume()
        isConnected = true
        listen()
    }
    
    func send(_ text: String) {
        let message = Message(text: text, isIncoming: false)
        messages.append(message)
        
        let wsMessage = URLSessionWebSocketTask.Message.string(text)
        webSocketTask?.send(wsMessage) { error in
            if let error = error {
                print("                

Блоги

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