Владислав Вчерашний, iOS-разработчик, рассказал об интересных изменениях, которые произошли с WebSockets в iOS 13.
Использование WebSockets в iOS никогда не было простым и прозрачным механизмом, как того хотелось бы. Но настал прекрасный момент и на ежегодной конференции для разработчиков WWDC 2019 компания Apple показала нам новый инструмент для работы с WebSockets – URLSessionWebSocketTask (наследник класса URLSessionTask).
В этой небольшой статье мы познакомимся с данным нововведением и узнаем – что изменится в будущем и станет ли теперь работа с WebSockets проще и понятнее.
Вступление
В наше время существует огромное количество приложений и большинство из них так или иначе использует REST API для “общения” с сервером. Мы уже давно привыкли, что для отправки или получения данных с сервера нужно просто сформировать запрос нужного типа и ожидать ответ. В большинстве случаев этого достаточно, верно?
А что, если вам необходимо реализовать “real-time” чат (некоторые умудряются сделать его с помощью Push Notification (!) но это совсем другая история) или автообновляемую ленту новостей (как в Twitter)? Достичь этих целей, конечно, можно и с помощью “повторяемых” запросов с определённой периодичностью, вот только недостатков у такого решения намного больше чем преимуществ (ресурсы, трафик, да и совсем нет гарантий по скорости получения ответа от сервера).
К счастью для решения подобных задач у нас есть WebSockets, где между клиентом и сервером существует постоянное и непрерывное соединение. Простыми словами – приложение подключается к выделенному порту сервера и после этого может отправлять ему данные или “слушать” входящие сообщения.
Но самое главное – каждый подключенный “клиент” мгновенно получает сообщения от сервера и без каких-либо дополнительных запросов.
URLSession – что нового?
Давайте посмотрим в класс URLSession. Как вы могли заметить –– здесь появилось 3 новых метода, которые необходимы для подключения к серверу:
@available(iOS 13.0, *) open func webSocketTask(with url: URL) -> URLSessionWebSocketTask @available(iOS 13.0, *) open func webSocketTask(with url: URL, protocols: [String]) -> URLSessionWebSocketTask @available(iOS 13.0, *) open func webSocketTask(with request: URLRequest) -> URLSessionWebSocketTask
Приступая к работе
Для начала нам необходимо подключиться к серверу. Поскольку URLSessionWebSocketTask является наследником URLSessionTask – их набор API весьма схожий:
func connect() {
guard !self.isConnected else { return }
self.session = URLSession(configuration: .default, delegate: self, delegateQueue: nil)
self.webSocketTask = self.session.webSocketTask(with: URL(string: self.baseURL)!)
self.webSocketTask.resume()
}
Отправка и получение сообщений
Для отправки сообщений следует использовать метод Send, который в качестве аргумента принимает URLSessionWebSocketTask.Message. Отправить можно только String или Binary data. Для получения нужно использовать Receive.
public enum Message {
case data(Data)
case string(String)
}
public func send(_ message: URLSessionWebSocketTask.Message, completionHandler: @escaping (Error?) -> Void)
public func receive(completionHandler: @escaping (Result<URLSessionWebSocketTask.Message, Error>) -> Void)
Пример реализации методов Send и Receive
func sendSignal(message: String) {
self.webSocketTask.send(.string(message)) { [weak self] error in
guard let error = error else { return }
self?.handleError(error.localizedDescription)
}
}
private func receive() {
self.webSocketTask.receive { [weak self] result in
switch result {
case .failure(let error):
self?.handleError("Failed to receive message: \(error.localizedDescription)")
case .success(let message):
switch message {
case .string(let text):
self?.logSignal("Received text message: \(text)")
case .data(let data):
print("Received binary message: \(data)")
@unknown default:
fatalError()
}
}
self?.receive()
}
}
Закрытие соединения
Чтобы отключиться от сервера достаточно вызвать метод Cancel. Он принимает два параметра – код закрытия (для информирования о причине закрытия соединения) и reason payload.
func disconnect() {
guard self.isConnected else { return }
self.webSocketTask.cancel(with: .goingAway, reason: nil)
}
Сыграем в Ping-Pong?
Иногда сервер может разорвать соединение если он не получает сигнал с определенной периодичностью. Чтобы “разбудить” сервер нужно использовать метод sendPing.
func sendPing() {
self.webSocketTask.sendPing { [weak self] error in
guard let `self` = self else { return }
if let error = error {
self.handleError(error.localizedDescription)
}
}
}
Заключение
Использование WebSockets в iOS 13 стало действительно простым, к тому же без необходимости подтягивания сторонних библиотек. Но главный момент в том, что новый API доступен только для iOS 13. Для более старых версий – извините, пользуйтесь дополнительными библиотеками (Starscream, Socket.IO или SocketRocket).
Мне нравится то, как развивается Network в iOS. Путь, который выбрала компания, не идеальный но, как по мне, он ведет в правильном направлении. В конечном счете мы придем к тому, что у нас будет доступно все из коробки.
Финальный код доступен по ссылке здесь.

