Thursday 21 July 2011

Tutorial: JSON Over HTTP On The iPhone




This tutorial provides a step-by-step howto for consuming JSON web services from an iPhone app.
When you’ve finished the tutorial you’ll have created a simple, back-of-a-fortune-cookie inspired, “Lucky Numbers” app with a solitary label whose content will be updated to show a list of lucky numbers fetched from a JSON-over-HTTP web service.

for download sample code go this link: 

https://github.com/dcgrigsby/luckynumbers/blob/master/Classes/JSON/SBJsonWriter.m

Source/Github

The code for this tutorial is available in GitHub. To see the source for the finished project, clone the repository:
  1. Open a terminal and change to the directory where you want the code
  2. Clone the repo by typing git clone git://github.com/dcgrigsby/luckynumbers.git

iPhone JSON Library

This tutorial uses Stig Brautaset’s JSON library (version 2.2), which provides functionality for parsing and generating JSON. We won’t be using the JSON generating functionality in this tutorial.
The library provides two methods for parsing JSON: (1) a high-level category that extends NSString to include JSON parsing and (2) a more granular, slighly lower level object-based parser. We’ll start simple and use the former; we’ll switch to the latter near the end of the tutorial.
  1. Download the disk image
We’ll include the relevant bits into our project in a later step.

Creating The Project

Launch Xcode and create a new View-Based iPhone application called LuckyNumbers:
  1. Create a new project using File > New Project… from Xcode’s menu
  2. Select View-Based Application from the iPhone OS > Application section, click Choose…
  3. Name the project as LuckyNumbers and click Save

Adding JSON Support To The Project

In order to use the JSON functionality we’ll need to add it to the project:
  1. Expand the LuckyNumbers project branch in the Groups & Files panel of the project
  2. Using Finder, locate the JSON_2.2.dmg file you downloaded earlier and mount it by double clicking its icon. A new finder window with the DMG’s contents will open
  3. Drag and drop the JSON directory from the DMG onto the Classes folder under the LuckyNumbersproject icon in the Groups & Files panel in Xcode
We’ll test that the JSON is set up properly by parsing a JSON dictionary string and using the resulting NSDictionary as a datasource for a message we’ll write to the console from our app’s viewDidLoad. This is throw-away code just to test that everything’s wired up properly.
Use this code for LuckyNumbersViewController.m (located in the project’s Classes Xcode folder):
#import "LuckyNumbersViewController.h"
#import "JSON/JSON.h"

@implementation LuckyNumbersViewController

- (void)viewDidLoad {
 [super viewDidLoad];
 NSString *jsonString = [NSString stringWithString:@"{\"foo\": \"bar\"}"];
 NSDictionary *dictionary = [jsonString JSONValue];
 NSLog(@"Dictionary value for \"foo\" is \"%@\"", [dictionary objectForKey:@"foo"]);
}

- (void)dealloc {
    [super dealloc];
}

@end
Build and run the project. If the JSON SDK is configured properly you should see an entry in the console that says, Dictionary value for “foo” is “bar”.

Setting Up The (Spartan) UI

Our finished app will need a UILabel to display the lucky numbers that we’ll eventually grab using HTTP and JSON.
Use this code for LuckyNumbersViewController.h (located in the project’s Classes Xcode folder):
#import <UIKit/UIKit.h>

@interface LuckyNumbersViewController : UIViewController {
 IBOutlet UILabel *label;
}

@end
IBOutlet is a macro that tells the compiler that it needs to wire up this variable to a correspondingUILabel element added WYSIWYG in Interface Builder. In the next step, we’ll add that element and connect the two pieces:
Edit the LuckyNumbersViewController.xib file in Interface Builder:
  1. Expand the Resources folder under the LuckyNumbers project branch in the Groups & Files panel.
  2. Double-click the LuckyNumbersViewController.xib file
Make sure that the Library, Inspector and View windows are open/visible. If they are not:
  1. Show the Library window using Tools > Library from the menu
  2. Show the Inspector window using Tools > Inspector from the menu
  3. Show the View by clicking the View icon on the LuckyNumbersViewController.xib window
Add the label:
  1. Locate the Label component in the Library window and drag it onto the view
  2. In the View window use the label’s handles to enlarge it to about half the size of the view
  3. In the Inspector window under View Attributes (the left-most tab), set the label’s number of lines to 0.
Setting the label’s number of lines to zero configures the label dynamically size the text to fit within its bounds.
Connect the Interface Builder label the code’s label. While still in Interface Builder:
  1. Control-click on File’s Owner icon in the LuckyNumbersViewController.xib window
  2. In the resulting pop-up menu, click-and-hold (i.e., don’t unclick) on the right-justified circle in thelocationLabel row of the Outlets section
  3. Drag the mouse to the Label in the View. A blue line will connect the two.
Confirm that the two have been connected. The pop-up menu should look like the image on the right.
If everything looks right, save the changes and close Interface Builder.

Fetching JSON Over HTTP

We’ll use Cocoa’s NSURLConnection to issue an HTTP request and retrieve the JSON data.
Cocoa provides both synchronous and asynchronous options for making HTTP requests. Synchronous requests run from the application’s main runloop cause the app to halt while it waits for a response. Asynchronous requests use callbacks to avoid blocking and are straightforward to use. We’ll use asynchronous requests.
First thing we need to do is update our view controller’s interface to include an NSMutableData to hold the response data. We declare this in the interface (and not inside a method) because the response comes back serially in pieces that we stitch together rather than in a complete unit.
Update LuckNumbersViewController.h. Changes are in bold:
#import <UIKit/UIKit.h>

@interface LuckyNumbersViewController : UIViewController {
 IBOutlet UILabel *label;
 NSMutableData *responseData;
}
To keep things simple, we’ll kick off the HTTP request from viewDidLoad.
Replace the contents of LuckyNumbersViewController.m with:

#import "LuckyNumbersViewController.h"
#import "JSON/JSON.h"

@implementation LuckyNumbersViewController

- (void)viewDidLoad {
 [super viewDidLoad];

 responseData = [[NSMutableData data] retain];
 NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:@"http://www.unpossible.com/misc/lucky_numbers.json"]];
 [[NSURLConnection alloc] initWithRequest:request delegate:self];
}

- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response {
 [responseData setLength:0];
}

- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
 [responseData appendData:data];
}

- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error {
 label.text = [NSString stringWithFormat:@"Connection failed: %@", [error description]];
}

- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
 [connection release];
}

- (void)dealloc {
    [super dealloc];
}

@end

This mostly boilerplate code initializes the responseData variable to be ready to hold the data and kicks off the connection in viewDidload; it gathers the pieces as they come in in didReceiveData; and the emptyconnectionDidFinishLoading stands ready to do something with the results.

Using The JSON Data

Next, we’ll flesh out the connectionDidFinishLoading method to make use of the JSON data retrieved in the last step.
Update the connectionDidFinishLoading method in LuckyNumbersViewController.m. Use this code:
- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
 [connection release];

 NSString *responseString = [[NSString alloc] initWithData:responseData encoding:NSUTF8StringEncoding];
 [responseData release];

 NSArray *luckyNumbers = [responseString JSONValue];

 NSMutableString *text = [NSMutableString stringWithString:@"Lucky numbers:\n"];

 for (int i = 0; i < [luckyNumbers count]; i++)
  [text appendFormat:@"%@\n", [luckyNumbers objectAtIndex:i]];

 label.text =  text;
}
Our earlier throw-away test code created an NSDictionary. Here it creates an NSArray. The parser is very flexible and returns objects — including nested objects — that appropriately match JSON datatypes to Objective-C datatypes.

Better Error Handling

Thus far, we’ve been using the the convenient, high-level extensions to NSString method of parsing JSON. We’ve done so with good reason: it’s handy to simple send the JSONValue message to a string to accessed to the parsed JSON values.
Unfortunately, using this method makes helpful error handling difficult. If the JSON parser fails for any reason it simply returns a nil value. However, if you watch your console log when this happens, you’ll see messages describing precisely what caused the parser to fail.
It’d be nice to be able to pass those error details along to the user. To do so, we’ll switch to the second, object-oriented method, that the JSON SDK supports.
Update the connectionDidFinishLoading method in LuckyNumbersViewController.m. Use this code:
- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
 [connection release];

 NSString *responseString = [[NSString alloc] initWithData:responseData encoding:NSUTF8StringEncoding];
 [responseData release];

 NSError *error;
 SBJSON *json = [[SBJSON new] autorelease];
 NSArray *luckyNumbers = [json objectWithString:responseString error:&error];
 [responseString release]; 

 if (luckyNumbers == nil)
  label.text = [NSString stringWithFormat:@"JSON parsing failed: %@", [error localizedDescription]];
 else {
  NSMutableString *text = [NSMutableString stringWithString:@"Lucky numbers:\n"];

  for (int i = 0; i < [luckyNumbers count]; i++)
   [text appendFormat:@"%@\n", [luckyNumbers objectAtIndex:i]];

  label.text =  text;
 }
}
Using this method gives us a pointer to the error object of the underlying JSON parser that we can use for more useful error handling.

No comments:

Post a Comment