How to use WebSockets on iOS using Swift

How to use WebSockets on iOS using Swift
Photo by John Barkiple

From Wikipedia,

WebSocket is a computer communications protocol, providing full-duplex communication channels over a single TCP connection. Although very useful on Web using JavaScript, we don't use it a lot on iOS platform.

From MDN,

The WebSocket API is an advanced technology that makes it possible to open a two-way interactive communication session between the user's browser and a server. With this API, you can send messages to a server and receive event-driven responses without having to poll the server for a reply.

However, there are definitely cases where WebSockets is useful on the iOS client side. Applications include updates for stocks prices, product price and quantity updates, and even game score updates at a regular interval.

Recently, I was interested in learning more about them since I was given a problem statement that explicitly involves working with sockets to get real-time data from APIs - In this case stock ticker data.

Problem Statement:

Let me begin with a brief problem statement. Users are presented with a list of trading options they can choose from. Once they make a selection, they go to the next screen where they can see real-time prices of selection. Options could be Gold price, currency exchange rate, individual stocks, or stock indices such as Sensex, Germany 30, etc.

Foundation for the Solution:

Unlike regular HTTP requests which fire and forget, our WebSocket opens the connection, subscribes to data, receives the data through events until the connection is either closed or unsubscribe event is registered.

Here is the step by step flow for connection,

This connection stream continues until the connection is cut-off or WebSocket is closed.

Implementation

With a foundation in place, let's begin work on implementation. Although iOS provides WebSocket support through URLSessionWebSocketTask, I decided to use a third-party library since it had better documentation and was easy to use compared to what Apple offered.

The third-party library I am using is, SwiftWebSocket. It's easy to use, provides clean examples, and is simple to test. With a flowchart in place, let's see how we can use it to trigger WebSocket connection and receive quote prices in real-time.

WebSocket URL

For this exercise, I am using the web socket URL provided by the BUX platform. this tutorial follows all the conventions provided by it. Since different URLs have different standards while establishing connections and sending messages, you might want to change this implementation based on the convention followed by your WebSocket URL.

wss://rtf.beta.getbux.com/subscriptions/me*

*Also need to request authorization information for this endpoint

Installation

To install SwiftWebSocket in your project, please add the following entry to your podfile and run pod install or pod update


target '<project_name>' do
  use_frameworks!
  pod 'SwiftWebSocket'

  target '<project_name>Tests' do
    inherit! :search_paths
  end
end

Importing module

To import module to your file, add the following line on top and you're ready to start,


import SwiftWebSocket

Opening Connection

Now with our basics cleared, let's write a code to open the WebSocket,


import SwiftWebSocket

var webSocket: WebSocket?
var opened = false
var connected = false

private func openWebSocket(urlString: String) {
    if let url = URL(string: urlString) {
        var request = URLRequest(url: url)
        
        // Add custom header parameters if necessary
        request.addCustomHeaders()
        
        let webSocket = WebSocket(request: request)
        
        webSocket.event.open = { [weak self] in
            self?.opened = true
        }
        
        webSocket.event.close = { [weak self] (code, reason, clean) in
            guard let self = self else { return }
            self.webSocket = nil
            self.connected = false
            self.opened = false
        }
        self.webSocket = webSocket
    } else {
        webSocket = nil
    }
}

Let's see what we are doing in the above code,

  1. Function openWebSocket takes urlString as an input. This string represents the web socket endpoint we're getting data from
  2. We add the necessary header parameters to our request. Header parameters may include authorization information and keys etc.
  3. We initialize a new WebSocket instance with this URLRequest object and assign it to the property to maintain its lifetime throughout the application
  4. We also add a couple of calls backs to webSocket instance. First to notify the client that the socket has been successfully opened and setting opened flag to true
  5. Second, we also add a callback when the socket is closed. When closed, we nil out webSocket instance and set connected and opened properties to false

Connecting and Receiving Messages from endpoint

Once we successfully open the connection, the next step is to set up a callback to receive messages. Messages may include initial ping to connect and subsequent messages which may be trading quotes or values that change periodically over time after initial subscription is done.

Let's look at the code first and then we will go over it one by one,


....
...
openWebSocket()
....
..

webSocket.event.message = { [weak self] message in
    guard let self = self else { return }
    
    if let message = message as? String, let messageType = self.getMessageType(from: message) {
        switch(messageType) {
        case .connected:
            self.connected = true
            self.subscribeToServer(with: productID)
        case .tradingQuote:
            let currentQuote = self.getCurrentQuoteResponse(from: message)
            print("Current value of selected stock \(currentQuote.price)")
        }
    }
}
  1. First off, we open the webSocket and assign its value to the property webSocket to be used later
  2. With the web socket open, we are ready to receive messages from the server. To catch them, we add another callback to capture them
  3. Messages are just a combination of message type and metadata. We parse the message string and decide whether it indicates the successful connection or a real-time data
  4. If the message type is connected, then we successfully established the web socket connection and send another request to register to a specific product with productID so that we can periodically get its real-time values
  5. Once we subscribe to specific productID, we get another message in event message callback with type tradingQuote which indicates this payload contains the current value of the selected product. For example, selected stock or currency exchange
  6. We pass the message string to the utility method getCurrentQuoteResponse which parses that string and return the Quote object for further processing

Here is the code to subscribe to the server after a connection is successfully established,


private func subscribeToServer(with productID: String) {
    let subscriptionPayload = self.subscriptionPayload(for: productID)
    webSocket.send(subscriptionPayload)
}

We create a subscription payload with given productID and send it over to the opened webSocket. Once the server receives this payload, it will send the periodic data through the opened event.message channel. This data will be in the form of a string with type tradingQuote for this particular use-case.

Now with this in place, we will continue getting subscribed quote values as long as the connection is open.

Unsubscribing and Closing the web socket connection

If you no longer need to receive values over the web socket, it's time to unsubscribe (optional) and close the web socket connection. Opening a web socket is an expensive operation so you can keep it open as long as you want while subscribing to additional quotes, but if you know the user is navigating away from the section of the app that needs real-time values, you can safely close it off.


func unsubscribeFromQuote(for productID: String) {
    let payload = ["unsubscribeFrom": ["trading.product.\(productID)"]]
    if let jsonData = try? JSONSerialization.data(withJSONObject: payload, options: []) {
        if let jsonPayload = String(data: jsonData, encoding: .utf8) {
            webSocket?.send(jsonPayload)
        }
    }
}

To unsubscribe, we use the productID to construct unsubscription payload, convert it into a string, and send it over using opened webSocket connection.

Once unsubscription completes, we will close the webSocket and clean up the remaining part


webSocket?.close()
self.webSocket = nil
self.connected = false
self.opened = false

Summary

WebSocket may not have a day-to-day application, but it's a powerful tool to efficiently receive real-time periodic updates. Fortunately, iOS has built-in WebSocket support in form of URLSessionWebSocketTask which can be utilized without needing third-party support.

This article was my attempt to give you a glimpse at the basic working of WebSockets on Swift and iOS and how to use them. The examples given in this article may not apply to you, but basics remain the same. WebSocket has the following three core functionalities,

  1. Opening a connection
  2. Receiving and Sending messages over the connection
  3. Closing the connection

If you want to play more with how sockets work, you can directly use the BUX provided URL to fetch real-time stock values. Please note, it works best during European trading time and needs authorization information which you can request from platform API.

This is all I have for today. Next time I  will expand this topic on two fronts,

  1. Creating WebSocket using native iOS APIs
  2. Using iOS APIs to create a Web socket server

Thanks for reading and if you have additional comments, feedback or concerns, please feel free to reach out on Twitter @jayeshkawli