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.
Source/Github
The code for this tutorial is available in GitHub. To see the source for the finished project, clone the repository:
- Open a terminal and change to the directory where you want the code
- 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.
- 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:
- Create a new project using File > New Project… from Xcode’s menu
- Select View-Based Application from the iPhone OS > Application section, click Choose…
- 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:
- Expand the LuckyNumbers project branch in the Groups & Files panel of the project
- 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
- Drag and drop the JSON directory from the DMG onto the Classes folder under the LuckyNumbers project 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 corresponding UILabel
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:
- Expand the Resources folder under the LuckyNumbers project branch in the Groups & Files panel.
- Double-click the LuckyNumbersViewController.xib file
Make sure that the Library, Inspector and View windows are open/visible. If they are not:
- Show the Library window using Tools > Library from the menu
- Show the Inspector window using Tools > Inspector from the menu
- Show the View by clicking the View icon on the LuckyNumbersViewController.xib window
Add the label:
- Locate the Label component in the Library window and drag it onto the view
- In the View window use the label’s handles to enlarge it to about half the size of the view
- 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:
- Control-click on File’s Owner icon in the LuckyNumbersViewController.xib window
- In the resulting pop-up menu, click-and-hold (i.e., don’t unclick) on the right-justified circle in the locationLabel row of the Outlets section
- 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:@"https://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 empty connectionDidFinishLoading
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.
Conclusion
The JSON SDK and Cocoa’s built-in support for HTTP make adding JSON web services to iPhone apps straightforward.
Update: Ryan Daigle points out that using ObjectiveResource makes consuming Ruby on Rails back-ended JSON (or, for that matter, XML) web services almost effortless.
Please leave a comment if you encounter any errors, mistakes or typos. Thanks!
This is an awesome article. I’ve been building similar projects against XML APIs, but SBJSON looks easier than XML parsing.
Awesome article on something I was just preparing to research! thanks!
Good tip, sir. I prefer the OOP means myself.
thanks for that article, i’m a starting iphone dev and it was a good start !
Don’t forget that ObjectiveResource supports both XML and JSON deserialization as well. It provides an easy way to remotely send and receive object data.
Thanks, this was a great tutorial to get started. Kinda what I was hoping to find in an iPhone SDK book I bought. Think we might need an “iPhone SDK for web developers” book with more examples like this.
Cheers
Nice example. I am still curious about how to send image data wrapped in an JSON object to a remote server. Would you provide an example along those lines as an addition?
thanks!
This is a great sample. Very easy to follow and well thought out. Saw ObjectiveResource here and went to try it out. Pretty disappointing … has the right buzzwords but in the end, despite trying to simplify it … in the end the documentation is so poorly done and the samples so purely organized that it seemed like a bad joke …..
This is a great example and has been really helpful. So thanks.
In the sample code for LuckyNumbersViewController.m, however, I think I spotted a memory leak in the viewDidLoad method:
[[NSURLConnection alloc] initWithRequest:request delegate:self];
Thanks Dan for this nice tutorial,
Its running fine on iPhone SDK 2.2.1, but i am struggling to run this code on iPhone SDK 3.0, getting the following errors
Precompiling LuckyNumbers_Prefix.pch (3 errors)
In file included from /Users/Mac/Desktop/WorkSpace/JSONTestApp/LuckyNumbers/LuckyNumbers_Prefix.pch
In file included from /var/folders/6j/6jTbl8c-HbG0gN9itOO-V++++TI/-Caches-/com.apple.Xcode.501/CompositeSDKs/iphonesimulator-iPhoneSimulator.platform3.0-gqibsjkkszjuandlmzobwzbemkyi/System/Library/Frameworks/UIKit.framework/Headers/UIKit.h
error: syntax error before ‘AT_NAME’ token
error: syntax error before ‘}’ token
fatal error: method definition not in @implementation context
The error points to file UILocalizedIndexedCollation.h which is included in UIKit.h.
Code:
UIKIT_EXTERN interface UILocalizedIndexedCollation : NSObject
{
package
NSLocale *_locale;
NSArray *_sectionTitles;
NSArray *_sectionStartStrings;
NSArray *_sectionIndexTitles;
NSArray *_sectionIndexMapping;
}
I would request you to please have a look on this issue.
Your help is highly appreciated.
Thanks
i have the same 3.0 compile error – but cant seem to resolve the problem.
ok it seems adding the JSON code to your project resolves the code signing AND the AT_NAME compile error for SDK 3.0
I just followed the instruction here (reversing what I did in option2 and did option1)
https://code.google.com/p/json-framework/wiki/InstallationInstructions
*took me a while to remove “Additional SDKs” to finally get rid of the AT_NAME compile error.
Updated the tutorial to work with 3.0 to fix the problems noted by Mike and Ashutosh. Fixed the memory leak noted by Sam.
Hi Dan,
I am currently working on an i phone application……in this application i have to display my current location on map and then using the bounds[southwest(latitude,longitude),northeast(latitude,longitude) as request parameters to the sever(where the data is stored in a jason format) display markers on the map of all the locations that lie within those bounds.
rite now i have hardcoded the the my url in the NSURLRequest. can you help me with this.
I have coded for displaying a map and the current location marker.
i have also used your code to get jason data in (NSArray *luckyNumbers).
i guess i’l have to make use of HTTP post and GET. please help me with this.
Thanks Dan, this example was clear and worked great, thanks for any changes you might have made for 3.0. I tried to use ObjectiveResource for my project but had errors and found insufficient documentation to help me get rolling. My needs are just two requests for JSON data at this time and I have that functionality in place now.
Hi Dan
Thank you for the tutorial – short and to the point.
I would like to get to the HTTP headers that accompanied the response to a query sent via NSURLRequest.
For example, query “https://twitter.com/statuses/friends_timeline.json” returns the JSON data, but the HTTP headers contain additional info of interest, notably X-RateLimit-Remaining — https://apiwiki.twitter.com/Rate-limiting.
Is there a way to get this info?
It’s a very good tutorial, but you could simplify all the connection trouble by doing:
NSString *jsonPath = [[NSString alloc] initWithString:@”https://json.path.in/web”];
NSString *jsonFile = [[NSString alloc] initWithContentsOfURL:[NSURL URLWithString:jsonPath]];
and then
NSArray *jsonData = [[NSArray alloc] initWithArray:[jsonFile JSONValue]];
or
NSDictionary *jsonData = [[NSDictionary alloc] initWithDictionary:[jsonFile JSONValue]];
depending on how the json is formated… i currently use this, so i know it works…
initWithContentsOfURL simplifies all the connection trouble and setting delegates and stuff… the JSON library throws exceptions when the json file was badly loaded, so with a try catch you can manage errors…
Sergio: that method blocks, halting the ui while you wait for the connection. The method I used does not freeze the ui.
Ahh… never thought of that… as my app only displays the received data, its of no use for me to display the view before the json loads… anyway, i didnt know how to do asynchronous connections before, but i do now… thanks!
Rudi:
The headers for an HTTP connection are included in the NSHTTPURLResponse. The allHeaderFields message returns a dictionary of the headers.
If you were doing a synchronous request it’s easy to populate an NSHTTPURLResponse:
NSURLRequest *request = [NSURLRequest requestWithURL: url];
NSHTTPURLResponse *response;
[NSURLConnection sendSynchronousRequest: request returningResponse: &response error: nil];
With an asynchronous request you have to do a little more work. When connection:didReceiveResponse: is called, it is passed (as the second parameter) an NSURLResponse. You can cast it as an NSHTTPUrlResponse like so:
– (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response {
NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse*)response;
NSLog([[httpResponse allHeaderFields] description]);
//…
Turned my answer to Rudi’s question about accessing HTTP responses into a short blog post:
https://www.mobileorchard.com/accessing-http-headers-from-an-nsurlrequest/
??????? ????? ?????? ???, ?? ? ?????? ??????? ?? ????????. ?????????? ??????????, ??? ??? ???????. 🙂
thank you great tutorial!!
short and sweet, thanks for a solid tutorial.
Excellent tutorial. Thanks for such an excellent masterpiece.
This is a great tutorial and very useful software. Could anyone point me to an example of exactly how they handled the copyright information provided on the App Store submission for an app that used this utility? I’m about to submit my first app and I don’t want to mess it up. Thanks.
Thanks, the only tutorial I could find on the subject. Got it working in my app easily. Glad it is so easy to parse JSON on the iPhonel
Here is a good article for those of you who are developing enterprise iPhone applications and use IBM WebSphere Process Server:
https://www.ibm.com/developerworks/websphere/tutorials/1001_felix/index.html
Thanks , nice stuff
I think you are leaking some memory. According to Apple’s doc for NSURLConnection, you should be releasing the connection in both the connectionDidFinishLoading and connectionDidFailWithError methods.
[connection release];
You only release it if the connection succeeds.
You also need to release responseData if the connection fails
responseString also should be released after its used…
Thank You for tutorial. It was very nice for getting started.
It’s very simple and good for understanding JSON parsing tutorial for new bee like me.
Thank you so much.
Hi!
Im trying to put a simple twitter search option (so no sign-in) in my iphone
application, but don’t understand how to do so. All the resources seem to be for webbased applications.. Can you please help me? Thanks!