Websockets in iOS 13 using Swift and Xcode 11

Websockets in iOS 13, macOS 10.15, tvOS 13, watchOS 6 and Mac Catalyst have gained first-class citizen status in networking stack. Apple has finally added support in URLSession and for lower level in Network.framework for their platforms.

This time we will focus on implementing Websockets using URLSession capabilities.

Before iOS 13

Previosly if you wanted to use Websockets in Apple platforms you had to rely on CFNetwork which was added in iOS 2.0. It is using C based foundation streams. You then have to deal with pointers and memory allocation issues which is quite common in C language.

Another way was to use third party solutions like Starscream which I have described before.

Websockets using URLSession

Here are three ways how you can construct a Websocket using URLSessionWebSocketTask class provided by URLSession:

func webSocketTask(with: URL) -> URLSessionWebSocketTask
func webSocketTask(with: URLRequest) -> URLSessionWebSocketTask
func webSocketTask(with: URL, protocols: [String]) -> URLSessionWebSocketTask

Opening connection

To create and open Websocket connection:

let urlSession = URLSession(configuration: .default)
let webSocketTask = urlSession.webSocketTask(with: "wss://echo.websocket.org")
webSocketTask.resume()

Sending messages

When connection has been established you can send Data or String message using URLSessionWebSocketTask.send function. You need to construct message with URLSessionWebSocketTask.Message enum type.

let message = URLSessionWebSocketTask.Message.string("Hello World")
webSocketTask.send(message) { error in
  if let error = error {                
    print("WebSocket couldn’t send message because: \(error)")
  }
}

Receiving messages

To receive messages from the server you need to use URLSessionWebSocketTask.receive method. It accepts completion handler which is a Result of Message type.

webSocketTask.receive { result in
  switch result {
  case .failure(let error):
    print("Error in receiving message: \(error)")
  case .success(let message):
    switch message {
    case .string(let text):
      print("Received string: \(text)")
    case .data(let data):
      print("Received data: \(data)")
    }
  }
}

Be aware that if you want to receive messages continuously you need to call this again once you are done with receiving a message. One way is to wrap this in a function and call the same function recursively.

func receiveMessage() {
  webSocketTask.receive { result in
    switch result {
    case .failure(let error):
      print("Error in receiving message: \(error)")
    case .success(let message):
      switch message {
      case .string(let text):
        print("Received string: \(text)")
      case .data(let data):
        print("Received data: \(data)")
      }
      
      self.receiveMessage()                
    }
  }	
}

Pings and pongs

To keep connection active with the server it is a good approach to send PING message with an interval. You can achieve that with URLSessionWebSocketTask.sendPing function.

func sendPing() {
  webSocketTask.sendPing { (error) in
    if let error = error {
      print("Sending PING failed: \(error)")
    }

    DispatchQueue.main.asyncAfter(deadline: .now() + 10) {
      self.sendPing()
    }
  }
}

Here again you need to take care of the next PING sending yourself. Easiest way is to just use DispatchQueue or Timer functionality.

Close connection

Once you’re done and would like to close the Websocket connection you need to send a close code which is a URLSessionWebSocketTask.CloseCode enum type.

webSocketTask.cancel(closeCode: .goingAway, reason: nil)

Checking connection state

To monitor connection status you can use URLSessionWebSocketDelegate protocol. You can check once connection has been opened or closed.

/// connection disconnected
func urlSession(_ session: URLSession, 
                webSocketTask: URLSessionWebSocketTask,
                didCloseWith closeCode: URLSessionWebSocketTask.CloseCode,
                reason: Data?)

// connection established
func urlSession(_ session: URLSession,
                webSocketTask: URLSessionWebSocketTask,
                didOpenWithProtocol protocol: String?)

TL;DR

Apple has finally added Websockets as first-class citzen to its platforms. Of course there are small quirks and rough edges. For instance, you can’t receive messages continously, but you don’t need to mess with constructing Websocket frame anymore which is a big win.

Right now it is available only for latest betas and if you support older versions of iOS, tvOS, watchOS or macOS you need to think about backwards compatibility yourself.

Links