Владислав Вчерашний, 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. Путь, который выбрала компания, не идеальный но, как по мне, он ведет в правильном направлении. В конечном счете мы придем к тому, что у нас будет доступно все из коробки.
Финальный код доступен по ссылке здесь.