Implementing Real-Time Communication in iOS with WebSockets

Implementing Real-Time Communication in iOS with WebSockets

iOSSwift
Fix bugs faster! Log Collection Made Easy
START NOW!

In iOS, WebSockets enable real-time communication between a client (an iOS app) and a server. Unlike traditional HTTP connections which are stateless, short-lived and request data on demand, WebSockets use a single long-lived connection to send and receive data simultaneously. This significantly reduces response times which is crucial for apps that rely on instant updates and live data to support functions such as chat and multiplayer games.

Let’s start with some key characteristics of iOS WebSockets

  • Real-time communication: With WebSockets the client and server can exchange information instantly, supporting applications which require real-time updates for sports scores, financial markets or a chat app.
  • Bidirectional communication: WebSockets enable both the client and server to send and receive data simultaneously, which is essential for dynamic applications.
  • Persistent connection: With WebSockets the connection between client and server remains open all the time, eliminating the need to create a new connection for each interaction, which is the case with HTTP.
  • Efficiency: Transmitting small messages frequently is much more efficient due to the lightweight framing mechanism used by WebSockets.
  • Cross-platform support: The WebSocket protocol is standardized by the IETF (Internet Engineering Task Force) and supported by most modern web browsers and server-side platforms.
  • Reconnections and failures: WebSockets have many features for handling reconnections after network failures or server downtime, making them reliable.

So we know what WebSockets are, but how can we use them in our iOS application? Let’s take a look.

Setting up a WebSocket server

First we’ll need to set up a WebSocket server and we start by selecting the programming language and framework compatible with our tech stack.

Key things to check are scalability, performance, and community support, along with features like support for sub protocols, extensions, and security.

Below are some implementation libraries for creating a WebSockets server:

To demonstrate, we’re going to create a simple server that does a WebSocket implementation using Node.js.

Installation and configuration with Node.js

Ready? Let’s start by creating our project as follows:

# Install the Node.js (if not already installed)
brew install node

# Create a Node.js project
mkdir websocket-server
cd websocket-server
npm init -y

# Install 'ws' library
npm install ws

Below is a very basic implementation of our WebSocket server (server.js):

const WebSocket = require('ws');
const server = new WebSocket.Server({ port: 3000 });

server.on('connection', (socket) => {
  console.log('Client is connected');

  socket.on('message', (message) => {
    console.log(`Received the message: ${message}`);
  });

  socket.on('close', () => {
    console.log('Client is disconnected');
  });
});

Great, next we can make our server more secure by implementing some basic WebSockets security.

We’ll start by generating an SSL Certificate – for this we can use tools like OpenSSL or Let’s Encrypt, like this:

openssl req -x509 -newkey rsa:4096 -keyout key.pem -out cert.pem -days 365

Now let’s update our WebSocket server configuration by modifying the server code to enable the use of secure WebSocket connections, as below:

const https = require('https');
const fs = require('fs');

const server = https.createServer({
  key: fs.readFileSync('key.pem'),
  cert: fs.readFileSync('cert.pem'),
});

const wss = new WebSocket.Server({ server });

// WebSocket server setup remains the same

It’s important to ensure our iOS app uses wss:// and not ws:// in the WebSocket URL so it can handle the SSL integration appropriately in our URLSession implementation.

Deploy and test

Now we can deploy the WebSocket server in a secure environment to test the secure connection from our iOS app and start crafting our Realtime application.

Let’s take a look at how this is done and understand how we can create a WebSocket client using Swift.

Swift WebSocket integration in iOS

URLSession is mainly used to handle networking to support data tasks, (e.g. download and upload tasks) for HTTP communication.

Introduced in iOS 13, URLSessionWebSocketTask extends URLSessionTask to provide native support and offering the high level API needed for creating and managing WebSocket connections.

Let’s take a look at how this is done.

Creating a WebSocket connection in Swift

First, we need to import the Foundation framework in our class, like this:

import Foundation

Next we’ll create a URLSessionWebSocketTask by initializing a URLSessionWebSocketTask object with the WebSocket URL of our server:

let url = URL(string: "wss://bugfenderexample.com")!
let webSocketTask = URLSession.shared.webSocketTask(with: url)

Now we can establish and start the WebSocket connection:

webSocketTask.resume()

And send a WebSocket message to the server using the send function:

let messageObj = URLSessionWebSocketTask.Message.string("Hello, Server!")
webSocketTask.send(messageObj) { error in
    if let error = error {
        print("Error sending a message: \(error)")
    }
}

Plus receive the messages from the server using the receive function.

Here we’re using a closure approach to process the received incoming message:

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

As you can see in the example, the message can be of two types as we can see in the switch

  • .data: This typically represents binary data received over the WebSocket connection.
  • .string: This represents text data received over the WebSocket connection.

Once you get the data, your iOS application will need to process it and probably show it to the user or store it for later use. The actual data format depends on the Server implementation as the WebSockets protocol doesn’t define any standard format. So you could send some very simple data to more complex data collections encoded in JSON.

With us so far? Great. Let’s look at how we can handle connection events and errors.

Handling WebSocket connection events and errors

First we need to implement the Event Listeners from URLSessionWebSocketDelegate and set the instance of the delegate to the webSocketDelegate of the URLSession , as below:

class WebSocketHandler: NSObject, URLSessionWebSocketDelegate {
    // Implement the required methods for URLSessionWebSocketDelegate
}

let webSocketDelegate = WebSocketHandler()
URLSession.shared.webSocketDelegate = webSocketDelegate

We handle connection events by implementing methods.

Let’s start with the event when a connection is opened:

class WebSocketHandler: NSObject, URLSessionWebSocketDelegate {
    // Implement the required methods for URLSessionWebSocketDelegate
		func urlSession(_ session: URLSession, webSocketTask: URLSessionWebSocketTask, didOpenWithProtocol protocol: String?) {
		    print("WebSocket connection opened: \(protocol ?? "none")")
		}
}

Now we can add the code handling the event when a connection is closed (we might need to clean things up at this point):

class WebSocketHandler: NSObject, URLSessionWebSocketDelegate {
    // ...
		
		func urlSession(_ session: URLSession, webSocketTask: URLSessionWebSocketTask, didCloseWith closeCode: URLSessionWebSocketTask.CloseCode, reason: Data?) {
		    print("WebSocket connection closed: \(closeCode.rawValue), reason: \(reason?.string ?? "none")")
		}
}

Finally, we can check for errors using the error handler method:

func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
    if let error = error {
        print("WebSocket task complete with error: \(error)")
    }
}

Fantastic! Now we’ve set up our server, configured it with Node.js, tested it, created our Swift connections and set up our connection events so we’re almost there.

Testing and debugging WebSockets in iOS Swift

Let’s take a look at some tools and techniques for debugging WebSockets:

Browser developer tools: Browser developer tools can be used to inspect and test the WebSocket frames and message communication. This monitors the WebSockets connection and the data being sent and received over the network and helps identify any errors or unexpected behaviour.

Wireshark: This is a network traffic analysis tool that includes WebSocket communication and provides a detailed logs of WebSocket payloads.

Console logs in Xcode: We can use print statement and breakpoints in Xcode to log all the WebSocket events and messages going back and forth to help to identify the issues during development.

Unit tests for WebSocket functionality

Unit testing is crucial as it allows us to simulate different scenarios during the development. Let’s look at how we set that up.

Mocking the WebSocket Server

We need to create a mock WebSocket server using any testing library, like SwiftNIO for server-side Swift.

Using XCTest Framework

We can leverage the XCTest framework to write the unit tests in Xcode. We need to write the test cases to cover various aspects of WebSocket functionality, including connection creation, sending and receiving the messages, and error handling, as shown below:

import XCTest

class WebSocketTests: XCTestCase {
    func testWebSocketConnection() {
        // Assert that the connection is established.
    }

Using the XCTestExpectation

We can use XCTestExpectation to handle the asynchronous tasks like WebSocket call-backs. As shown below, it will wait for expectations to be fulfilled before completing the test of WebSocket communication:

func testWebSocketReceiveMessage() {
    let expectation = XCTestExpectation(description: "Received expectation")
    webSocketTask.receive { result in
        // Handle the received message
        expectation.fulfill()
    }
    wait(for: [expectation], timeout: 2.0)
}

That’s how we test and debug but before we go, let’s take a quick look at some best practices for testing real-time features:

  • Automation testing: Implement automation testing to cover various scenarios and different edge cases to enhance stability.
  • Mock external dependencies: Mocking the external dependencies (e.g. the WebSocket server) of the system is essential during testing.
  • CI Pipeline: Integrating the CI pipeline helps detect issues early in the development process.
  • Network conditions: Test WebSocket functionality under various network conditions, including low-bandwidth and high-latency scenarios to ensure the app behaves correctly in different scenarios.
  • Load testing: Perform load testing to assess the capacity of the WebSocket server to handle concurrent connections and messages.
  • Monitoring tools: Have some monitoring tools in place in the production environment to track WebSocket performance and identify potential issues.

And that’s it.

To sum up

WebSockets help iOS developers resolve the complexities of real-time communication to deliver quality experiences for users.

Whether your app is for chat, collaboration or sports scores, WebSockets will help you to achieve the real-time back and forth communication needed.

In this guide we learned how to:

  • Set up and configure a WebSockets server using Node.js
  • Enhance security with an SSL certificate
  • Implement and integrate WebSockets in iOS Swift with its lifecycle methods
  • Handle connection events and errors
  • Test and debug Websocket functionality

iOS WebSockets are a powerful tool for building responsive and interactive applications that require real-time communication between clients and servers.

They offer advantages over traditional HTTP-based communication in scenarios where low-latency, real-time updates, and efficient handling of network requests are essential.

Expect the Unexpected! Debug Faster with Bugfender
START FOR FREE

Trusted By

/assets/images/svg/customers/projects/slack.svg/assets/images/svg/customers/highprofile/ford.svg/assets/images/svg/customers/cool/airmail.svg/assets/images/svg/customers/projects/porsche.svg/assets/images/svg/customers/highprofile/tesco.svg/assets/images/svg/customers/highprofile/rakuten.svg/assets/images/svg/customers/cool/levis.svg/assets/images/svg/customers/highprofile/schneider_electric.svg

Already Trusted by Thousands

Bugfender is the best remote logger for mobile and web apps.

Get Started for Free, No Credit Card Required