iPhone Client

A chat client app for iOS devices is included in the ice-demos repository. Since Ice for Objective-C includes a server-side run time, this chat app can use the push model and wait passively for the server to deliver chat updates via callbacks. The sample code presented below gives you a taste of what it's like to write an Ice application in Objective-C.

Creating a Session

One of the first tasks of a push client is to establish a session with the Glacier2 router. The program presents the user with a login form that requests information such as the server host, user name, and password. Click on the thumbnail image to see the login form.

The client begins by creating a router proxy and configuring it as the default router for the communicator:

id<ICECommunicator> communicator = ...
NSString* s = @"Glacier2/router:...";
id<ICEObjectPrx> proxy = [communicator stringToProxy:s];
id<ICERouterPrx> router = [ICERouterPrx uncheckedCast:proxy];
[communicator setDefaultRouter:router];

It is safe to perform these actions in the main thread because they do not block. However, the remaining steps in the login process may block the calling thread and prevent the user interface from being updated in a timely manner, therefore the client spawns a separate thread dedicated to completing this initialization. The thread continues with the creation of a Glacier2 session:

id<Glacier2RouterPrx> glacier2router = [Glacier2RouterPrx checkedCast:router];
id<Glacier2SessionPrx> glacier2session =
    [glacier2router createSession:username password:password];
id<ChatChatSessionPrx> sess = [ChatChatSessionPrx uncheckedCast:glacier2session];

The client down-casts the router proxy to the derived interface Glacier2::Router using a checked cast to verify that the target object supports the expected interface. With the router's proxy in hand, the client can create a Glacier2 session. The createSession operation returns a proxy of type Glacier2::Session, and the client narrows this proxy to the derived interface Chat::ChatSession that we saw earlier. The client uses an uncheckedCast here because it already knows that the session object implements this interface.

After successfully creating the session, the client must take care of a little house-keeping before it can instantiate its callback object. The first step is to create an object adapter, which is the Ice construct responsible for dispatching incoming requests to Ice objects. Activating the object adapter allows the Ice run time to begin dispatching requests:

id<ICEObjectAdapter> adapter =
    [communicator createObjectAdapterWithRouter:@"ChatDemo.Client" router:router];
[adapter activate];

Every Ice object requires a unique identity, which is a structure consisting of two strings representing a name and a category. When using callbacks with Glacier2, the client must use a category supplied by Glacier2. The code below prepares the identity for the callback object:

ICEIdentity* callbackId =
    [ICEIdentity identity:[ICEUtil generateUUID]
                 category:[router getCategoryForClient]];

Now the client can instantiate its callback object. Since the client activated the object adapter earlier, it is possible for requests to be dispatched to this object as soon as we add it to the object adapter. In turn, the object adapter returns a proxy that we can down-cast to the appropriate interface:

id<ICEObjectPrx> proxy =
    [adapter add:[ChatRoomCallbackI
                    chatRoomCallbackWithTarget:self.chatViewController]
                 identity:callbackId];
id<ChatChatRoomCallbackPrx> callback =
    [ChatChatRoomCallbackPrx uncheckedCast:proxy];

The last step is to join the chat room, which we accomplish by invoking setCallback on the session:

[sess setCallback:callback];

Asynchronous Invocations

When an interactive program needs to perform a series of related but potentially blocking actions, it is convenient to group them together and execute them in a separate thread, as we did in the previous section to create a session. However, this strategy would quickly grow tedious in situations where you only need to make one remote invocation. Fortunately, Ice guarantees that invoking a remote operation asynchronously will never block the calling thread, and the iOS chat client uses this feature extensively. For example, the code below shows how the client invokes an asynchronous version of the send operation to publish a newly-typed chat message:

[session begin_send:s
            response:^(ICELong t) { }
            exception:^(ICEException* ex) { [self exception:ex]; }];

The first argument to begin_send is the string containing the new chat message, followed by Objective-C blocks that will be invoked when the operation completes successfully or raises an exception. Here the client ignores a successful response and calls exception if an error occurs.

The Ice run time invokes callbacks from outside the main thread, which can be a problem if a callback method needs to update the user interface. Normally the callback would consist of tedious code that arranges for the desired action to be performed in the main thread so that the user interface could be safely updated, but fortunately Ice Touch offers a very convenient alternative in the Dispatcher facility. To use this feature, we need to set the dispatcher member of ICEInitializationData prior to initializing our communicator:

ICEInitializationData* initData = [ICEInitializationData initializationData];
// ...
initData.dispatcher = ^(id call, id con)
{
    dispatch_sync(dispatch_get_main_queue(), ^ { [call run]; });
};
self.communicator = [ICEUtil createCommunicator:initData];

This dispatcher guarantees that all callbacks from the Ice run time are executed in the main thread.

Eventually the message published via begin_send will return to the client in the form of a chat update callback from the server. Our implementation of send instantiates a ChatMessage object and invokes append:

-(void)send:(ICELong)timestamp name:(NSMutableString *)name
    message:(NSMutableString *)message
{
    [self append:[ChatMessage chatMessageWithText:message who:name
        timestamp:timestamp]];
}

The append method adds the new message to a list that maintains the most recent 100 messages, then updates the user interface to display the message and ensure that the scrolling area is positioned correctly.

The image on the right shows the iPhone client's chat history. Click on the thumbnail to see the full image.