Введение: почему 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: