JSON-RPC in Objective-C

Handling JSON in Objective-C is already pretty easy with the excellent strict parser provided by json-framework. However, interacting with a web service in a graphical application can be hard to get right. Frequently unresponsive or unpredictable behavior is a big turnoff for users. Recently I have been developing a lot for the iPhone and more often than not, ideas that beget these applications revolve around central web applications. Many client-server interactions usually boil down to the familiar CRUD apps we’re all so familiar generating with Rails and Django but require much more effort.

Writing applications for a mobile device introduces several challenges: limited memory, slow network and a language and framework designed without an eye on modern agile web services. Recently while writing an iPhone application with heavy web interaction I got lazy doing all that work and wrote DeferredKit – a port of Twisted’s Deferred with many extensions for us Objective-C coders. DeferredKit makes asynchronous programming in Objective-C really easy with very little overhead and includes an asynchronous JSON-RPC client. It allows you to write code like this:

  - (void)doRegistration {
    [[[[[DKDeferred jsonService:SERVICE_URL]
       myApp] register:array_(self.username, self.password)] 
      addCallback:callbackTS(self, doRegistrationCompleted:)]
     addErrback:callbackTS(self, doRegistrationFailed:)];
  }
 
  - (id)doRegistrationCompleted:(id)result {
    // do something with result
    return result;
  }
 
  - (id)doRegistrationFailed:(NSError *)err {
    // do something with error
    return err;
  }

Jumping In

Getting started with DeferredKit is relatively easy. Download the or pull DeferredKit from github and drop everything from DeferredKit/CocoaDeferred/Source into your project (you can also compile DeferredKit as a direct dependency in Xcode, a how-to is in the README).

First, a bit of explanation. Despite the recent addition of blocks to the Objective-C language, the blocks runtime is not available to us iPhone developers (or anybody who wants to deploy to targets older than 10.6, sadly). Also, since there is no native function object in Objective-C, functions can not be passed around as first-class citizens, stored in collection types, persisted, etc. DeferredKit provides one called DKCallback. You will almost never have to touch the interface of DKCallback since handy macros are provided to generate function objects.

A deferred is defined as an object that encapsulates a return value that is not yet available (meaning, it will be returned from the network when done downloading, or return from a thread when done computing). The deferred then dispatches the value to a series of callbacks as soon as it becomes available.

JSON-RPC is exposed in DeferredKit through a class called DKJSONServiceProxy which employs a bit of magic to enable the dynamic method calling pattern you saw above. For convenience, autorelease constructors are also provided in DKDeferred:

  id service = [DKDeferred jsonService:@"http://myservice.net/json/" name:@"myapp.sayHello"];

(note: don’t forget to #import DKDeferred+JSON.h)

Calling a method returns a deferred:

  DKDeferred *d = [service :args_array];

To which you must add at least one callback for the call to be performed. In this case, callbackTS builds a callback with a target (can be any object), and a selector to be performed. A callback could also be a function pointer, NSInvocation or any object conforming to the DKCallback protocol.

  [d addCallback:callbackTS(self, gotSayHelloResults:)];

In case your JSON-RPC server returns an error (via the error key) or encounters a network error or parse error, deferred will automatically call any errbacks, or “error callbacks,” added with an NSError object.

  [d addErrback:callbackTS(self, handleSayHelloError:)];

Callbacks and errbacks are written as a function or method which takes a single argument, the result and returns id. JSON-RPC results will always be an NSDictionary with a result key, among others.

  - (id)gotSayHelloResults:(id)results {
    self.helloLabel.text = [results objectForKey:@"result"];
    return nil;
  }

Errbacks are the same as callbacks except they will always be called with an NSError argument. The userDict dictionary will be populated with the error dictionary produced by your JSON-RPC server. userDict will be different for different error types however, so be sure to check which kind of error is being returned.

  - (id)handleSayHelloError:(NSError *)err {
    [self alertError:[[[error userDict] objectForKey:@"error"] objectForKey:@"message"]];
    return nil;
  }

Conclusion

I’ve only had the opportunity to touch on the basics of DeferredKit, but a post is coming soon with more details. It is a tested, lightweight framework for writing asynchronous code and can greatly improve your productivity when working with web services.

Tags: , , , , ,

Leave a Reply

You must be logged in to post a comment.