In the previous post we looked at some basic usage of the Swift SignalR Client. This was enough to get started but far from enough for any real-world application. In this post we will look at features offered by the client that allow handling more advanced scenarios.
One very important detail we glossed over in the previous post was related to starting the connection. While starting the connection seems to be as simple as invoking:
it is not really the case. If you run the playground sample in one go you will see a lot of errors similar to:
2019-07-29T16:05:00.987Z error: Attempting to send data before connection has been started.
What’s going on here? The
start()method is a not blocking call and establishing a connection to the server requires sending some HTTP requests, so takes much more time than just running code locally. As a result, the playground code continues to run and try to invoke hub methods while the client is still working in the background on setting up the connection. Another problem is that there is actually no guarantee that the connection will be ever successfully started (e.g. the provided URL can be incorrect, the network can be down, the server might be not responding etc.) but the
start()method never returns whether the operation completed succcessfully. The solution to these problems is the
HubConnectionDelegateprotocol. It contains a few callbacks that allow the code that consumes the client be notified about the connection lifecycle events. The
HubConnectionDelegateprotocol looks like this:
The names of the callbacks should make their purpose quite clear but let’s go over them briefly:
raised when the connection was started successfully. Once this event happens it is safe to invoke hub methods. The hubConnection passed to the callback is the newly started connection
connectionDidFailToOpen(error: Error)– raised when the connection could not be started successfully. The error contains the reason of the failure
connectionDidClose(error: Error?)– raised when the connection was closed. If the connection was closed due to an error the error argument will contain the reason of the failure. If the connection was closed gracefully (due to calling the
stop()method) the error will be
nil. Once the connection is closed trying invoking a hub method will result in an error
To set up your code to be notified about hub connection lifecycle events you need to create a class that conforms to the
HubConnectionDelegate protocol and use the
HubConnectionBuilder.withHubConnectionDelegate() method to register it. One important detail is that the client uses a weak reference to the delegate to prevent retain cycles. This puts the burden of maintaining the reference to the delegate on the user. If the reference is not maintained correctly the delegate might be released prematurely resulting in missing event notifications.
The example chat application shows the usage of the lifecycle events. It blocks/unblocks the UI based on the events raised by hub connection to prevent the user from sending messages when there is no connection to the server. The
HubConnectionDelegate derived instance is stored in a class variable to ensure that the delegate will not be released before the connection is stopped.
HubConnectionBuilder is a helper class that contains a number of methods for configuring the connection:
withLogging– allows configuring logging. By default no logging will be configured and no logs will be written. There are three overloads of the
withLoggingmethod. The simplest overload takes just the minimum log level which can be one of:
When the client is configured with this overload all log entries at the configured or higher log level will be written using the
Loggerprotocol and registering it with one of the other
withHubConnectionDelegate– configures a delegate that allows receiving connection lifecycle events (described above)
withHttpConnectionOptions– allows setting lower level configuration options (described below)
withHubProtocol– used to set the hub protocol that the client will use to communicate with the server. Not very useful at the moment given that currently the only supported hub protocol is the Json hub protocol which is also used by default (i.e. no additional configuration is required to use this protocol)
HttpConnectionOptions class contains lower level configuration options set using the
HubConnectionBuilder.withHubConnectionOptions method. It allows configuring the following options:
accessTokenProvider– used to set a token provider factory. Each time the client makes an HTTP request (currently – because the client supports only the webSocket transport – this happens when sending the negotiate request and when opening a webSocket) the client will invoke the provided token factory and set the
AuthorizationHTTP header to:
skipNegotiation– by default the first step the client takes to establish a connection with a SignalR server is sending a negotiate request to get the capabilities of the server (e.g. supported transports), the connection id which identifies the connection on the server side and a redirection URL in case of Azure SignalR Service. However, the webSocket transport does not need a connection id (the connection is persistent) and if the user knows that the server supports the webSocket transport the negotiate request can be skipped saving one HTTP request and thus making starting the connection faster. The default value is
false. Note: when connecting to Azure SignalR service this setting must be set to
falseregardless of the transport used by the client
headers– a dictionary containing HTTP headers that should be included in each HTTP request sent by the client
httpClientFactory– a factory that allow providing an alternative implementation of the
HttpClientprotocol. Currently used only by tests
Azure SignalR Service
When working with Azure SignalR Service the only requirement is that the
HttpConnectionOptions.skipNegotiation is set to false. This is the default setting so typically no special configuration is required to make this scenario work.
Limits on the number of arguments
send methods have strongly typed overloads that take up to 8 arguments. This should be plenty but in rare cases when this is not enough it is possible to drop to lower level primitives and use functions that operate on arrays of items that conform to the
Encodable protocols. These functions work for any number of arguments and can be used as follows:
Variable number of arguments
The SignalR server does not enforce that the same client method is always invoked with the same number of arguments. On the client side this rare scenario cannot be handled with the strongly typed .on methods. In addition -similarly to the scenarios described above – there is a limit of 8 parameters that the strongly typed
.on callbacks support. Both scenarios can be handled by dropping to the lower level primitive which uses an
ArgumentExtractor class instead of separate arguments. Here is an example:
These are pretty much all the knobs and buttons that the Swift SignalR Client currently offers. Knowing them allows using the client in the most effective way.