Чем отличается WebSocket от URLSessionDataTask в iOS?

«Чем отличается WebSocket от URLSessionDataTask в iOS?» — вопрос из категории Сети, который задают на 10% собеседований IOS Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

URLSessionDataTask — это задача для выполнения одноразовых HTTP/HTTPS-запросов по модели "запрос-ответ". Соединение устанавливается, данные передаются, и затем оно закрывается.

WebSocketTask (через URLSession) — это задача для установки двустороннего (full-duplex) постоянного соединения по протоколу WebSocket (ws:// или wss://). После "рукопожатия" (handshake) канал остается открытым для обмена сообщениями в реальном времени.

Сравнительная таблица:

Аспект URLSessionDataTask WebSocketTask
Модель связи Запрос-ответ (Request-Response) Постоянное двустороннее соединение
Инициатор Всегда клиент Любая сторона (клиент или сервер) может отправить данные в любой момент
Протокол HTTP/HTTPS WebSocket (поверх TCP, начинается как HTTP)
Накладные расходы Заголовки HTTP с каждым запросом Минимальные, после установки соединения передается только payload
Типичное использование Загрузка данных REST API, изображений, JSON Чат, онлайн-игры, биржевые тикеры, коллаборативные приложения

Пример WebSocket в Swift:

import Foundation

class WebSocketManager: NSObject, URLSessionWebSocketDelegate {
    private var webSocketTask: URLSessionWebSocketTask?

    func connect() {
        let url = URL(string: "wss://echo.websocket.events")!
        let session = URLSession(configuration: .default, delegate: self, delegateQueue: OperationQueue())
        webSocketTask = session.webSocketTask(with: url)
        webSocketTask?.resume()
        receiveMessage() // Начинаем слушать сообщения
    }

    func send(_ message: String) {
        let message = URLSessionWebSocketTask.Message.string(message)
        webSocketTask?.send(message) { error in
            if let error = error { print("Send error: ", error) }
        }
    }

    private func receiveMessage() {
        webSocketTask?.receive { [weak self] result in
            switch result {
            case .success(let message):
                switch message {
                case .string(let text):
                    print("Received: ", text)
                case .data(let data):
                    print("Received data: ", data)
                @unknown default:
                    break
                }
                self?.receiveMessage() // Рекурсивно слушаем следующее сообщение
            case .failure(let error):
                print("Receive error: ", error)
            }
        }
    }

    func disconnect() {
        webSocketTask?.cancel(with: .normalClosure, reason: nil)
    }
}

Пример DataTask:

func fetchData(from url: URL, completion: @escaping (Result<Data, Error>) -> Void) {
    URLSession.shared.dataTask(with: url) { data, response, error in
        if let error = error {
            completion(.failure(error))
            return
        }
        guard let data = data else { return }
        completion(.success(data))
    }.resume()
}