Introduction to WebSocket in Swift: From Theory to Advanced Practice
In modern mobile development, real-time interaction has become not just a feature, but a necessity. Users expect chats, news feeds, quotes, and notifications to update instantly, without unnecessary pull-to-refresh gestures. WebSocket is the protocol that allows you to establish a permanent bidirectional connection between the client (your Swift application) and the server.
In this article, we will go beyond a simple "Hello, World!" and explore advanced techniques for working with WebSocket in Swift for iOS and macOS platforms. You will learn how to set up a reliable connection, handle complex scenarios (connection loss, reconnection, background mode) and build an architecture that can withstand the load of a real stock exchange application or a chat with thousands of users.
We will cover both the native URLSessionWebSocketTask (iOS 13+) and the popular Starscream library for older versions. The code will be fully functional and ready for integration.
1. Advanced WebSocket Client Setup in Swift
Let's start with the fundamentals. Instead of a trivial connection, we will create a manager that manages the connection lifecycle, handles errors, and supports reconnect (automatic reconnection).
1.1. Manager with Automatic Reconnection
Let's implement a WebSocketManager class that uses URLSessionWebSocketTask. The key feature is an exponential backoff delay between reconnection attempts to avoid overloading the server.
import Foundation
final class WebSocketManager: NSObject { private var webSocketTask: URLSessionWebSocketTask? private var session: URLSession? private var isConnected = false private var reconnectAttempt = 0 private let maxReconnectDelay: TimeInterval = 30 // Maximum delay 30 sec private let baseDelay: TimeInterval = 2 // Base delay 2 sec var onMessageReceived: ((String) -> Void)? var onConnectionStateChanged: ((Bool) -> Void)? func connect(url: URL) { session = URLSession(configuration: .default, delegate: self, delegateQueue: OperationQueue()) let request = URLRequest(url: url, timeoutInterval: 10) webSocketTask = session?.webSocketTask(with: request) webSocketTask?.resume() receiveMessage() } private func reconnect() { guard let url = webSocketTask?.originalRequest?.url else { return } let delay = min(baseDelay * pow(2.0, Double(reconnectAttempt)), maxReconnectDelay) print("Reconnecting in \\(delay) seconds... (Attempt \\(reconnectAttempt + 1))") DispatchQueue.global().asyncAfter(deadline: .now() + delay) { [weak self] in self?.connect(url: url) self?.reconnectAttempt += 1 } } private func receiveMessage() { webSocketTask?.receive { [weak self] result in switch result { case .success(let message): switch message { case .string(let text): self?.onMessageReceived?(text) case .data(let data): if let text = String(data: data, encoding: .utf8) { self?.onMessageReceived?(text) } @unknown default: break } self?.reconnectAttempt = 0 // Reset counter on successful receive self?.receiveMessage() // Continue listening case .failure(let error): print("Receive error: \\(error.localizedDescription)") self?.disconnect() self?.reconnect() } } } func send(text: String) {