Asynchronous Calls (Cocoa)
Unlike its counterparts for Delphi and .NET, where most calls are made synchronously and async requests are a rare corner-case, the Cocoa and Coca Touch frameworks used on Mac and iPhone greatly encourage asynchronous code, so asynchronous requests become much more important on these platforms. For this reason, Remoting SDK for Cocoa provides extensive and well-rounded support for them, by generating asynchronous proxy code by default and through the ROAsyncRequest that handles them at runtime.
Asynchronous requests are started just like any regular RO request, except that you use the Async
version of the proxy that was generated in your Intf file and call one of the begin*
methods it defines. For each operation defined in your service, the proxy will contain two begin*
methods – one that merely takes all the in
parameters of your operation, and a second that takes an optional BOOL start
parameter.
The begin*
methods will complete immediately and not wait for the server request to complete, and return an ROAsyncRequest object that represents the new request. If you call the version of your begin*
method that does not take a start
parameter, the request will already be started and being sent to the server at this stage. If instead you passed start:NO
, the request will not start until you send a start
message. This allows you to do additional configuration on the request.
The following two code snippets illustrate this:
MyService_AsyncProxy *myService = [AsyncProxy proxyWith...];
ROAsyncRequest *ar = [myService beginMyMethodCall:parameter1];
// request is now running.
MyService_AsyncProxy *myService = [AsyncProxy proxyWith...];
ROAsyncRequest *ar = [myService beginMyMethodCall:parameter1 start:NO];
// request is not running yet.
[ar setDelegate:self];
[ar setContext:@"MyContext"];
[ar start];
Two common things to do before starting an asynchronous request are to set the delegate
that will receive callbacks (more on that later), or to set a custom context
object that will allow you to uniquely identify the particular request later. If you do not assign a delegate, the ROAsyncRequest will inherit the delegate set on the Client Channel.
The context
reference is purely for your own reference use and will not be handled by the request itself. Like the delegate, it will not be retained, so it is up to you as the caller to make sure the lifetime of the context object extends past the request (if necessary by retaining/releasing it yourself).
Delegate Methods
The delegate object assigned to a ROAsyncRequest should implement the ROAsyncRequestDelegate protocol, which has two essential methods, as well as a few optional ones. The two essential methods are:
- (void)asyncRequestDidComplete:(ROAsyncRequest *)request;
- (void)asyncRequest:(ROAsyncRequest *)request didFailWithException:(NSException *)exception;
The first message asyncRequestDidComplete: serves as your notification that the background request has been completed and will usually be used to obtain the result and any out parameters of the call by calling the end*
method matching the begin*
that was called prior.
If your delegate is shared between different requests, you can use the request's context
property to refer to any arbitrary context object you assigned before to determine how to proceed handling the request.
The second, asyncRequest:didFailWithException: will be sent to your delegate if the request failed, for example due to a communication problem. It will also be sent if your implementation of asyncRequestDidComplete: caused an exception (for example if the server returned an exception, which will get thrown on your call to end*
, that your code does not handle).
Note:
Even though async requests may leverage background threads internally (depending on the channel in use), all delegate messages will be sent on the thread that the request was started on, unless the request was started with a startInBackground rather than a start message. If startInBackground was used, delegate messages will be sent on an arbitrary thread, as seen fit by the underlying channel.
Authentication in Asynchronous Requests
As you are probably aware, Remoting SDK usually handles Authentication and Login transparently in the background, by catching the relevant server exceptions within the channel, firing the necessary clientChannelNeedsLogin: message, and resuming your call uninterruptedly, if login was successful. This allows your client code to make calls without having to worry about or deal with interruptions due to authentication.
The same is true for asynchronous requests, but due to the non-blocking nature, some advanced handling is in place.
Just as with synchronous requests, an initial async call may fail with the server demanding login. Async requests do not handle server-side exceptions until you call the end*
method to retrieve the operation result, so the (failed) call will send the usual asyncRequestDidComplete: message to your delegate object, prompting a call to end:
in your code. This call will throw the SessionNotFoundException sent back by the server, which in turn triggers a series of events, internally:
- the call to
end*
returns to your code, throwing a special version of the SessionNotFoundException exception. You should not catch this exception, but instead let it bubble up the exception chain, bypassing the rest of your asyncRequestDidComplete: handler. - the client channel will perform login by sending the asyncRequestNeedsLogin: or clientChannelNeedsLogin: delegate methods, depending on which handler is implemented in your code.
- if login is successful, the client channel will transparently
restart
your original request. - once the restarted request has completed, your delegate receives another asyncRequestDidComplete:, giving your code a second shot at calling
end*
and processing the results.
While this handling differs slightly from synchronous calls, where authentication is totally hidden from the calling code, it is a necessary evil in order to avoid blocking your asyncRequestDidComplete: (and with that, most likely, your application's main thread) while login is taking place.
However, by keeping two small things in mind when implementing your asyncRequestDidComplete: handler, this will work seamlessly and intuitively:
- be aware that asyncRequestDidComplete: might be called multiple times, and do not perform any actions before calling
end*
that depend on the call succeeding. It is usually best to callend*
as first action in your handler. - do not blindly catch exceptions, in particular the SessionNotFoundException in your handler (you can use asyncRequest:didFailWithException: to be notified of exceptions, instead).
Note: the asyncRequestNeedsLogin: or clientChannelNeedsLogin: will be sent to your delegate on a background thread, in order to not block the main thread while the (usually synchronous) login takes place. If you need to show user interface as part of this handler, make sure to marshal to the main thread, accordingly. Alternatively, your channel's delegate can implement clientChannelNeedsLoginOnMainThread: (and not clientChannelNeedsLogin:!), in order to be invoked on the main thread – but be aware that this will block your main UI thread while your Login call is in progress.
Asynchronous Requests in Data Abstract
Data Abstract for Cocoa provides its own layer of support for asynchronous data requests, based on what is described here. See Asynchronous Requests in Data Abstract and the DAAsyncRequest class for details.