Thursday, October 15, 2015

New Client APIs for iOS and OS X

When the iOS SDK came out in 2009, our choice at Lightstreamer was to build a new client library from scratch. We took as a reference the Java client library and started developing an entirely new Objective-C codebase, trying to merge the best object-oriented design we had at that time with a new language and environment.

This had both advantages and disadvantages. On one side, native integration with iOS led to good performance and useful features, like network availability monitoring to avoid battery drainage. On the other hand, the slow process of discovering and fixing corner-conditions and bugs, which the Java library had passed through already, had to be repeated all over again. Moreover, as new features were introduced in the server, they had to be introduced in the client libraries multiple times.

For these reasons, when the opportunity to rewrite the iOS client came in, we decided to try a different approach: transpile (i.e. trans-compile) a single Java codebase, in common with the Java client (now mostly dedicated to Android) and fill in the gaps with well designed wrappers and dependency injection. We explored this possibility before, but tools were not mature enough. Now they are, trans-compilaiton is a reality thanks to j2objc, an impressive tool made by the Google team, and transpiled code works and performs very well.

So, today we are proud and excited to announce the availability of the alpha version of iOS and OS X client libraries version 2.0, both developed from a highly engineered codebase in common with the Java/Android client. The library's core is written in Java, transpiled to Objective-C and then wrapped and integrated with especially written native code, providing a natural and seamless interface to iOS and OS X developers.

Version 2.0 adheres to the so-called Lightstreamer Unified Client API Model, i.e. an API designed to be the same across all environments: Javascript, Java/Android, iOS/OS X, .NET and so on. Developers will be able to switch between different platforms sure to find the same objects and methods, behaviors and features.

Let's see how it works and how to port your code.

Note: if you need download and example pointers, jump directly to the end of the post ("Get the SDKs").

Importing the Client Library

Beginning with version 2.0, iOS and OS X client libraries are distributed via CocoaPods. If you haven't tried this dependency manager yet, take this opportunity to give it a try, as it makes things much more simple. At time of writing both libraries have reached 2.0.0 alpha 4.

Importing the Library via CocoaPods

To install CocoaPods, read its Getting Started guide. Then, follow these simple steps:

  • If you haven't created your podfile yet, do it by running pod init from a terminal positioned in your project's directory.
  • Edit your Podfile and add the following line:
    pod 'Lightstreamer_iOS_Client', '2.0.0-a4'
  • Alternatively, for an OS X app, add the following line: 
    pod 'Lightstreamer_OS_X_Client', '2.0.0-a4'
  • Run pod install and CocoaPods will download, verify and integrate the requested client library in your project.
  • Close Xcode and reopen it with the workspace that CocoaPods just created i your project's directory.
  • Finally, add the following import line wherever you need the client library's services:
    #import <LightstreamerClientAll.h>

Importing the Library Traditionally

If CocoaPods is not for you, you can still install the library more traditionally by following these steps:

  • Download the iOS client library's distribution and docset (or OS X client library's distribution and docset).
  • Unzip the library's distribution and copy the include and lib folders inside your project.
  • In your project's Build Phases page, please add the following libraries in the Link Binary With Libraries section:
    libjre_emul.a
    libLightstreamer_iOS_client_64.a
  • For an OS X app, in place of the last library please add:
    libLightstreamer_OSX_client.a
  • Specify the include folder you copied above in your project's Build Settings page, in the Header Search Path setting.
  • In Build Settings page, also add the following flags in the Other Linker Flags settings:
    -lz 
    -licucore
  • Finally, add the following import line wherever you need the client library's services:
    #import <LightstreamerClientAll.h>

Connecting

Connecting to a Lightstreamer server with version 1.x was a matter of creating an LSConnectionInfo object and then calling the openConnectionWithInfo:delegate: method on an LSClient instance. The call to openConnectionWithInfo:delegate: was blocking and could end up with an exception. E.g.,

@try {
    LSConnectionInfo *connectionInfo= [LSConnectionInfo
        connectionInfoWithPushServerURL:PUSH_SERVER_URL
        pushServerControlURL:nil
        user:@"user1"
        password:nil
        adapter:ADAPTER_SET];

    [_client openConnectionWithInfo:connectionInfo delegate:self];

@catch (NSException *e) {
    // ...
}

Version 2.0 changes slightly the interface. The main object is now called LSLightstreamerClient, and connection options can be set in two properties of any instance of this object: connectionOptions and connectionDetails respectively. E.g.,

_client[[LSLightstreamerClient alloc]
    initWithServerAddress:PUSH_SERVER_URL
    adapterSet:ADAPTER_SET];
_client.connectionDetails.user= @"user1";


Once the client is set up, a delegate can be added with the addDelegate: method and the connection can be started up with the connect method. An important difference is that now this call is non-blocking (i.e. asynchronous) and no exceptions are thrown for run time error conditions, only for programmer errors. E.g.,

[_client addDelegate:self];
[_client connect];

Changes to the Connection Delegates

The LSConnectionDelegate protocol of version 1.x had a different event for each different status the connection could enter. E.g.,

- (void) clientConnection:(LSClient *)client
    didStartSessionWithPolling:(BOOL)polling {
    // ...
}

- (void) clientConnection:(LSClient *)client 
    didChangeActivityWarningStatus:(BOOL)warningStatus {
    // ...
}

- (void) clientConnectionDidEstablish:(LSClient *)client {
    // ...
}

The new LSClientDelegate protocol of version 2.0, instead, has just one event for any status change of the connection:

- (void) client:(nonnull LSLightstreamerClient *)client 
    didChangeStatus:(nonnull NSString *)status {
    // ...
}

Possible statuses are well documented in the API docs and are much more descriptive. For example, they include the following:
  • "CONNECTING"
  • "CONNECTED:STREAM-SENSING"
  • "CONNECTED:HTTP-STREAMING"
  • "CONNECTED:HTTP-POLLING"
  • "STALLED"
  • "DISCONNECTED:WILL-RETRY"
  • "DISCONNECTED"
With this change, it's easier to track how the connection evolves and react accordingly. By the way, did you notice we added nullability annotations to each and every method call?

Subscribing

With version 1.x a subscription was made by creating an appropriate table descriptor, such as LSExtendedTableInfo, and calling the corresponding subscribeTable:delegate:useCommandLogic: on the LSClient. Similarly to the connection case, here also the method call was blocking and could throw exceptions. E.g.,

@try {
    LSExtendedTableInfo *tableInfo= [LSExtendedTableInfo 
        extendedTableInfoWithItems:TABLE_ITEMS
        mode:LSModeMerge
        fields:LIST_FIELDS
        dataAdapter:DATA_ADAPTER
        snapshot:YES];
    _tableKey= [_client subscribeTableWithExtendedInfo:tableInfo
        delegate:self
        useCommandLogic:NO];
} @catch (NSException *e) {
    // ...
}

With version 2.0, subscriptions are all created with the same descriptor, an LSSubscription instance. Once the descriptor properties are set, a delegate can be added with the usual addDelegate: method and the subscription can be started by calling the subscribe:  method on LSLightstreamerClient. E.g.,

LSSubscription *subscription[[LSSubscription alloc]
    initWithSubscriptionMode:@"MERGE"
    items:TABLE_ITEMS 
    fields:LIST_FIELDS];
subscription.dataAdapter= DATA_ADAPTER;
subscription.requestedSnapshot= @"yes";

[subscription addDelegate:self];
[_client subscribe:subscription];

Similarly to the connection case, here also the call to subscribe: is non-blocking and no exceptions are thrown, except for programmer errors.

Changes to the Subscription Delegate

In the LSTableDelegate of version 1.x the item update event, the most important event, had this signature:

- (void) table:(LSSubscribedTableKey *)tableKey 
    itemPosition:(int)itemPosition 
    itemName:(NSString *)itemName 
    didUpdateWithInfo:(LSUpdateInfo *)updateInfo {
    // ...
}

The LSSubscriptionDelegate of version 2.0 is roughly equivalent. The item update event has now the following signature:

- (void) subscription:(nonnull LSSubscription *)subscription 
    didUpdateItem:(nonnull LSItemUpdate *)itemUpdate {
    // ...
}

Similarly, the LSItemUpdate object, carrying the update information, is roughly equivalent to its version 1.x counterpart LSUpdateInfo. Name changes were necessary to keep the API aligned with other clients adhering to the Unified Client API.

Sending Messages

The method signatures for sending messages are almost unchanged between the two versions. Anyway, similarly to previous cases, the important change is that in the new version method calls are non-blocking and no exceptions are thrown.

This means that where in version 1.x we had to spawn a separate thread to avoid blocking the UI (the example shows Swift code):

func textFieldShouldReturn(textField: UITextField) -> Bool {
    // ...

    dispatch_async(_queue) {
        self._client.sendMessage("CHAT|" + message)
    }
}

With version 2.0 the call can be made in-line as it will not block the UI even in case of connection problems (again Swift code):

func textFieldShouldReturn(textField: UITextField) -> Bool {
    // ...

    self._client.sendMessage("CHAT|" + message)
}

Conclusion

While version 2.0 is not yet complete, as it still lacks support for push notifications (it is marked "alpha" precisely for this reason), a great effort has been spent so far to improve the interface, taking inspiration from the successes of the JavaScript client library, and create a better codebase in common with the Android version.

We are convinced that improved delegates, asynchronous calls, simplified error handling and, last but not least, easier integration via CocoaPods, will quickly pay off the effort needed on the customer side to update their apps' code to the new version.

Let us know your thoughts.

Get the SDKs

You can download the SDKs, which include the API reference documentation, as part of the Lightstreamer distribution from lightstreamer.com/download.

Lightstreamer Client Library for iOS

Requirements

  • Includes both 32 and 64 bit code (and i386 code for use with the simulator)
  • Requires iOS version 5.1 or greater
  • Requires linking with the following libraries and frameworks: libz.dylib, libicucore.dylibSecurity.framework, libjre_emul.a (included)
  • Requires ARC

Lightstreamer Client Library for OS X

Requirements

  • Requires OS X version 10.7 or greater
  • Requires linking with the following libraries and frameworks: libz.dyliblibicucore.dylibSecurity.frameworklibjre_emul.a (included)
  • Requires ARC

No comments:

Post a Comment

Note: Only a member of this blog may post a comment.