Channels iOS Swift v9.0.0: Refreshing the SDK with a flexible, native WebSocket client

Swift-SDK-v9.jpg

With Channels iOS Swift SDK v9.0.0 we've implemented a native WebSocket client to tackle closed connection troubleshooting and speed up future development.

Introduction

We’ve released a new version of the Channels iOS Swift SDK. v9.0.0 is now live. Check out what’s changed and how Senior iOS Engineer Dan Browne tackled updates to tighten connection closure troubleshooting and speed up future development…

I joined Pusher over the summer as the new Senior iOS Engineer. Going forward, I’ll be responsible for maintaining the Channels Swift SDK and Beams Swift SDK . Part of my brief since joining has been to propose and implement quality of life updates to the Swift SDKs that can serve as the foundation on which to build future features.

One of the first things I identified was the fact that the Channels Swift SDK was inconsistent with our other SDKs when it came to features for troubleshooting connection closures. This led me down a rabbit hole of third-party dependency management, mocking for unit tests, and the pros and cons of maintaining backwards compatibility for older OS versions.

TL;DR

You can read the full story below, but here’s a summary of what’s changed:

  • We’ve simplified the internals of our Channels Swift SDK.
    • The v9.0.0 release changes the minimum OS version requirements.
    • We’ll be continuing v8.x releases for critical fixes (until October 1st 2021), but we strongly suggest upgrading to v9.0.0 as soon as possible.
  • The Channels Swift SDK now fully supports our Protocol closure codes.
    • In most cases, this had had no impact at all for customers.
    • In rare circumstances, when a connection unexpectedly closed it could make it more difficult for us to troubleshoot the reason behind the issue.
    • We waited to deliver a fully-native WebSocket Client.
      * Avoids limitations that prevented supporting our Protocol closure codes.
      * We wanted to maximise iOS ecosystem penetration given we were changing backwards compatibility.

Respecting WebSocket closure codes

When closing a WebSocket connection, a server will send a closure code to indicate the reason for the connection ending. Several standard codes are defined in the WebSocket protocol, but there are also reserved ranges for different use cases. The Channels Protocol defines several custom codes in the 4000 – 4299 range. Whilst each code defines a specific reason for a connection closure, the codes are divided into three ranges indicating the reconnection logic that should be used by a Client.

The fact that the Channels Swift SDK didn’t respect our own closure codes means that it had been inconsistent with our other Client SDKs. Whilst this had no impact to the vast majority of users, addressing this brings consistency and will aid troubleshooting if issues with connections do occur.

Starscream is a popular open source library for managing WebSocket connections on iOS and macOS, written in Swift. In looking into the root cause, I found that the Channels Swift SDK was using an old version of Starscream that doesn’t expose custom closure codes easily. Originally, it seemed that simply upgrading to the latest release would allow me to resolve the issue. However, this introduced an issue that would have meant a large refactor of how our unit tests mocked our WebSocket class, based on changes to the Starscream API.

Whilst our tests could have been refactored, the more important issue was that behind the scenes Starscream uses the URLSessionWebSocketTask API. This is a very simple native API for managing WebSocket connections introduced in iOS 13.0 and macOS 10.15. However, it unfortunately does not expose custom closure codes on connection closures due to the fact that the URLSessionWebSocketTask.CloseCode enum only defines the standard protocol closure codes.

The solution to have the Channels Swift SDK respect custom closure codes was to go another level deeper.

A flexible, native WebSocket client

It seems that the URLSessionWebSocketTask API is built on the lower-level NWProtocolWebSocket API (which is part of the Network framework, and was introduced at the same time). Unlike its high-level counterpart, the NWProtocolWebSocket.CloseCode enum defines cases for custom closure codes.

This meant that with some trial and error (the documentation for the Network framework was a little spartan) I was able to implement a WebSocket client that exposes the exact closure code reported when a connection ends. In turn, this has meant that we were able to ensure the Channels Swift SDK now respects Pusher Channels Protocol closure codes when choosing when and how to reconnect to the server.

An additional bonus of this approach has been that a third-party dependency has been removed. Using open source libraries can be useful for many applications to solve complex problems, however in this case implementing a WebSocket Client using native APIs is relatively straightforward. The dependency graph of the SDK has therefore been simplified and a limitation of a third-party dependency has also been worked around.

Backwards compatibility

Until now, the Channels Swift SDK has had fairly extensive backwards compatibility. Version 8.0.0 of the SDK supports iOS 8.0 and above, macOS 10.11 and above, and tvOS 9.0 and above. This of course has the advantage of supporting a wider range of devices, but it comes at the expense of additional developer overhead (in the best case scenario) or has the potential to limit future development. This issue has been an example of the latter scenario.

The eagle-eyed reader will notice that the native APIs discussed above have only been made available in recent OS releases, and therefore are not compatible with maintaining such a wide range of older OS releases. We’ve taken the decision to release our new native WebSocket client (and the associated fix to respect our own closure codes) as part of version 9.0.0 of the Channels Swift SDK. The reason for the jump to a new major version is motivated by having to change our minimum supported OS versions as follows:

  • iOS 13.0 and above
  • macOS 10.15 and above
  • tvOS 13.0 and above

This allows us to deliver the native WebSocket Client and the closure codes bug fix. Though this represents a shift, supporting a narrower range of older OS versions for our Client SDKs will mean we can move faster in the future. We’re also narrowing the range of minimum supported OS versions in line with the distribution of iOS usage as reported by Apple in June 2020 (almost a year after the iOS 13.0 release):

iPhone

  • iOS 13.x: 92% of devices 4yrs old or newer (81% of all devices).
  • iOS 12.x: 7% of devices 4yrs old or newer (13% of all devices).
  • Earlier: 2% of devices 4yrs old or newer (6% of all devices).

iPad

  • iOS 13.x: 93% of devices 4yrs old or newer (73% of all devices).
  • iOS 12.x: 6% of devices 4yrs old or newer (16% of all devices).
  • Earlier: 1% of devices 4yrs old or newer (11% of all devices).

Apple has not released any stats following the recent iOS 14.0 release, however stats from popular mobile analytics tool Bugfender suggest this trend will continue:

iOS 14.x: 37%

iOS 13.x: 54.4%

Given it is soon after the release of iOS 14.0, the important figure here is the sum of iOS 14.x and 13.x usage. In general, supporting back to the previous major OS release captures over 90% of the iOS ecosystem (rising to around 99% for newer devices). We believe that is a reasonable tradeoff, if it means we can deliver new features more quickly.

Maintenance plan

For the time being, two development tracks will be maintained. Critical bug fixes will be applied to both the v8.x and v9.x releases. These bug fixes will be available as part of v8.x releases until October 1st 2021. These same fixes will be made available in parallel in v9.x and above releases. After that date, development will cease on v8.x releases.

What do users need to do?

The most important thing to note is there are no public API changes in v9.0.0 of the SDK, so upgrading from v8.x should be seamless. If you are an existing customer using the Channels Swift SDK, it is strongly encouraged that you upgrade to v9.0.0 of the Channels Swift SDK as soon as possible. This will ensure you have access to all the latest features and bug fixes now and in the future.