SignalR-Client-Swift is a SignalR client for the Core version of SignalR for applications written in Swift. It’s been around for a while and, although the work is still in progress, it is stable and usable enough to use it in real apps. The project is an open source project hosted on GitHub and has received number of contributions from the community (e.g. including big features like support for Swift Package Manager). Unfortunately, so far, the documentation for this client has been between scarce and non-existent making it harder to adopt it. This and the next post aim to fix this problem.
Before looking diving into code let’s talk about the current state of affairs. As I mentioned, the work is far from finished and some features you can find in other clients are not currently supported. The following is the list of major SignalR features that are currently not implemented:
- Long Polling and Server Sent Events transports
- non-Json based hub protocols (e.g. Message Pack)
- restartable connections
- KeepAlive messages
Here is the more positive list of major features that are implemented:
- webSockets transport
- client and server hub method invocations (using Json hub protocol)
- streaming methods
- support for Azure SignalR Service
- authentication with auth tokens
SignalR for ASP.Net Core is not backwards compatible with the previous version of SignalR. Hence, the Swift SignalR client will not work with the non-Core version of SignalR server.
Installation
The first step to use the client in a project is installation. Currently there are three ways to install Swift SignalR Client into your project:
CocoaPods
Add the following lines to your Podfile
:
use_frameworks! | |
pod 'SwiftSignalRClient' |
Then run:
pod install
Swift Package Manager (SPM)
Add the following to your Package dependencies:
.package(url: "https://github.com/moozzyk/SignalR-Client-Swift", .upToNextMinor(from: "0.6.0")),
Then include "SignalRClient"
in your target dependencies. For example:
.target(name: "MySwiftPackage", dependencies: ["SignalRClient"]),
Manually
Pull the code from the GitHub repo and configure SignalR client as an Embedded Framework.
Usage
Once the client has been successfully installed it is ready to use. The usage of the Swift SignalR Client does not differ much from other existing clients – you need to create a hub connection instance that you will use to connect and talk to the server. Note that you need to use the same instance of the client for the entire lifetime of your connection.
The easiest way to create a HubConnection
instance is to use the HubConnectionBuilder
class which contains a number of methods that allow configuring the connection to be created. For instance, creating a HubConnection
instance with logging configured at the debug level would look like this:
let hubConnection = HubConnectionBuilder(url: URL(string: "http://localhost:5000/playground")!) | |
.withLogging(minLogLevel: .debug) | |
.build() |
Creating a hub connection does not automatically start the connection. It just creates an instance that will be used to communicate with the server once the connection is started. This pattern makes it possible to register handlers for the client-side methods without risking missing invocations received between starting the connection and registering the handler. Handlers for the client-side methods are registered with the on
method as follows:
hubConnection.on(method: "AddMessage") {(user: String, message: String) in | |
print(">>> \(user): \(message)") | |
} |
It is worth noting that types for the handler parameters must be specified and must be compatible with the types of values sent by the server (e.g. if the server invokes the method with a string the parameter type of the handler cannot be Int
). The number of handler parameters should match the number of arguments used to invoke the client-side method from the server side.
After registering handlers it’s time to start the connection. It is as easy* as:
hubConnection.start() |
From this point on, if the connection was started successfully, the handlers for the client-side methods will be invoked whenever the method was invoked on the server. Starting the connection allows also to invoke hub methods on the server side. (Trying to invoke a hub method on a non-started connection results in an error). SignalR supports two kinds of hub methods – regular and streaming. When invoking a regular hub method, the client may choose to be notified when the invocation has completed and receive the result of invocation (if the hub method returned any) or an error in case of an exception. Below are examples of such invocations:
// invoking a hub method and receiving a result | |
hubConnection.invoke(method: "Add", 2, 3, resultType: Int.self) { result, error in | |
if let error = error { | |
print("error: \(error)") | |
} else { | |
print("Add result: \(result!)") | |
} | |
} | |
// invoking a hub method that does not return a result | |
hubConnection.invoke(method: "Broadcast", "Playground user", "Sending a message") { error in | |
if let error = error { | |
print("error: \(error)") | |
} else { | |
print("Broadcast invocation completed without errors") | |
} | |
} |
When invoking a hub method that returns a result providing the type of the result is mandatory and this type has to be compatible with the type of the value returned by the hub method. Also, there is no distinction between local and remote handlers – i.e. the completion handler will be called with an error not only when the method on the server side fails but also when initiating the invocation fails (e.g. when trying to invoke a method when the connection is not running).
Hub methods can also be invoked in a fire-and-forget manner. When invoking a hub method in this fashion the client will not be notified when the invocation has completed and will not receive any further events related to this method – be it a result or an error. The code below shows how to invoke a hub method in a fire-and-forget manner:
hubConnection.send(method: "Broadcast", "Playground user", "Testing send") { error in | |
if let error = error { | |
print("Send failed: \(error)") | |
} | |
} |
Note, that the send method still takes a callback that allows handling errors but this callback will be called only for local errors – i.e. errors that occurred when sending data to the server.
SignalR streaming hub methods return a (possibly infinite) stream of items. Each time the client receives a new stream item a user provided callback will be invoked with the received value.
When a streaming method completes executing a completion callback will be invoked (except for this bug which I found writing this post). The client method that invokes streaming hub methods returns a stream handle. This handle can be used to cancel the streaming hub method. The following code snippet illustrates how to invoke and cancel a streaming hub method:
let streamHandle = hubConnection.stream(method: "StreamNumbers", 1, 10000, itemType: Int.self, | |
streamItemReceived: { item in print(">>> \(item!)") }) { error in | |
print("Stream closed.") | |
if let error = error { | |
print("Error: \(error)") | |
} | |
} | |
DispatchQueue.main.asyncAfter(deadline: .now() + .seconds(2)) { | |
hubConnection.cancelStreamInvocation(streamHandle: streamHandle) { error in | |
print("Canceling stream invocation failed: \(error)") | |
} | |
} |
If you no longer want to receive notifications from the server or invoke hub methods you can disconnect from the server with:
hubConnection.stop() |
One final note about types of arguments and results. The types of all the values sent to the server must conform to the Encodable
protocol. The types for the values returned from the server must conform to the Decodable
protocol. The most common types in Swift already conform to the Codable
protocol (which means that they conform to both the Encodable
and the Decodable
protocols) and when creating custom structs/classes it is easy to make them conform to the Codable
protocol as long as all the member variables already conform to the Codable
protocol.
These are the basics of the SignalR Swift Client. The project repo contains additional resources in form of example applications for macOS and iOS. I also created a Swift playground which contains all code snippets published in this post. In the next post we will look at the connection lifecycle events, available configuration options and more advanced scenarios.
* – it is actually not entirely true but we will return to it in the second post†
[…] the previous post we looked at some basic usage of the Swift SignalR Client. This was enough to get started but far […]
LikeLike