2011年10月29日星期六

Working with JSON in iOS 5 Tutorial

Working with JSON in iOS 5 Tutorial:
Learn how you can easily read and write JSON in iOS 5!
Learn how you can easily read and write JSON in iOS 5!

Note from Ray: This is the eighth iOS 5 tutorial in the iOS 5 Feast! This tutorial is a free preview chapter from our new book iOS 5 By Tutorials. Enjoy!

This is a blog post by iOS Tutorial Team member Marin Todorov, a software developer with 12+ years of experience, an independent iOS developer and the creator of Touch Code Magazine.

iOS 5 has some new built-in APIs to make it really easy to read and write JSON.

If you don’t know what JSON is, it’s a simple human readable format that is often used to send data over a network connection.

For example, if you have an array of three strings, the JSON representation would simply be:

["test1", "test2", "test3"]

If you have a Pet object with member variables name, breed, and age, the JSON representation would simply be:

{"name" : "Dusty", "breed": "Poodle", "age": 7}

It’s that simple, which is why it’s so easy and popular to use. For the full spec, which can be read in just a couple minutes, check out www.json.org.

The reason JSON is important is that many third parties such as Google, Yahoo, or Kiva make web services that return JSON formatted data when you visit a URL with a specified query string. If you write your own web service, you’ll also probably find it really easy to convert your data to JSON when sending to another party.

In this tutorial, we’ll cover how you can work with JSON in iOS 5. Keep reading to see how simple it is!



JSON and iOS 5


If you’ve had to parse JSON in your iOS apps in the past, you’ve probably used a third party library such as JSON Framework.

Well with iOS 5, needing to use a third party library for JSON parsing is a thing of the past. Apple has finally added a JSON library in Cocoa and I must say I personally like it very much!

You can turn objects like NSString, NSNumber, NSArray and NSDictionary into JSON data and vice versa super easily. And of course no need to include external libraries – everything is done natively and super fast.

In this chapter we’re going to get hands-on experience with the new native JSON support. We’re going to build a simple application which will connect to the Internet and consume JSON service from the Kiva.org web site. It will parse the data, show it in human readable format on the screen, and then build back a different JSON.

descr

Later on in the chapter we’re going to create a class category which will give you ideas how to integrate JSON support more tightly into Cocoa and your own classes. Having the possibility to turn your own classes data into JSON could really help you persist data structures online, exchange data between applications, or anything that requires your classes to be able to serialize and persist data, which can be sent over http, email, etc.

Getting Started


Open up Xcode and from the main menu choose File\New\New Project. Choose the iOS\Application\Single View Application template, and click Next. Name the product KivaJSONDemo, select iPhone for the Device family, and make sure just the Use Automatic Reference Counting checkbox is checked, click Next and save the project by clicking Create.

Let’s do a little bit of clean up first – open up ViewController.m file and replace everything inside with this :


#define kBgQueue dispatch_get_global_queue(
DISPATCH_QUEUE_PRIORITY_DEFAULT, 0) //1
#define kLatestKivaLoansURL [NSURL URLWithString: 
@"http://api.kivaws.org/v1/loans/search.json?status=fundraising"] //2

#import "ViewController.h"

@implementation ViewController

@end


In the first line of code we define a macro that gives us back a background queue – I like having a kBgQueue shortcut for that, so I can keep my code tighter.

In the second line of code we create a macro named kLatestKivaLoansURL which returns us an NSURL pointing to this URL [http://api.kivaws.org/v1/loans/search.json?status=fundraising].

Go ahead and visit this URL in your browser if you want – you’ll see Kiva.org’s list of currently fundraising loans in JSON format. We’re going to use this API to read the list of loans, take the latest one and show the information on the screen.

Let’s make a little detour and design the application’s UI in Interface Builder real quick.

Open ViewController.xib in the Project Navigator. This app is supposed to be positive and life-changing, so we need to do something about that background! Select it and from the Utilities bar (1), make sure you have the Attributes Inspector open (2), and set the Background to a nice green color (3).

descr

Now grab a label from the Object library and drop it inside the already open view (1). Resize the label so it fits about 4 lines of text and takes almost the screen’s width (2). Then from the Attributes Inspector make the following changes: set “Background” to white, set “Lines” to “5″ (3). Click on the label to make sure it’s selected and then press Cmd+C, Cmd+V to clone the label (4). Finally arrange the two labels like on the screenshot:

descr

To polish up the interface add 3 more labels and finish the UI so it looks like this:

descr

The only thing left is to connect our labels to a couple of IBOutlets in our class. Switch to ViewController.h and add two instance variables inside the interface:


@interface ViewController : UIViewController {
IBOutlet UILabel* humanReadble;
IBOutlet UILabel* jsonSummary;
}


Then open up ViewController.xib again. Control-drag from “File’s owner” onto the 1st 5-line label and from the popup menu choose humanReadable.

Again while holding the “ctrl” key drag with the mouse from “File’s owner” onto the 2nd 5-line label and from the popup menu choose jsonSummary.

That concludes the project setup – we’re ready to start coding!

Parsing JSON from the Web


The first thing we need to do is download the JSON data from the web. Luckily, with GCD we can do this in one line of code! Add the following to ViewController.m:


- (void)viewDidLoad
{
[super viewDidLoad];

dispatch_async(kBgQueue, ^{
NSData* data = [NSData dataWithContentsOfURL:
kLatestKivaLoansURL];
[self performSelectorOnMainThread:@selector(fetchedData:)
withObject:data waitUntilDone:YES];
});
}


Remember how earlier we defined kBGQueue as a macro which gives us a background queue?

Well this bit of code makes it so that when viewDidLoad is called, we run a block of code in this background queue to download the contents at the Kiva loans URL.

When NSData has finished fetching data from the Internet we call performSelectorOnMainThread:withObject:waitUntilDone: so we can update the application’s UI. We haven’t written fetchedData: yet but will do so shortly.

Remember it is only OK to run a synchronous method such as dataWithContentsOfURL in a background thread, otherwise the GUI will seem unresponsive to the user.

Also, remember that you can only access UIKit objects from the main thread, which is why we had to run fetchedData: on the main thread.

Note: You might wonder why I preferred to use performSelectorOnMainThread:withObject:waitUntilDone: over dispatching a block on the main thread? It’s a personal preference really and I have two reasons:

I’m all for the greatest readability of a piece of code. For me, [self performSelectorOnMainThread:...] makes it easier to spot what’s going on in that piece of code.

I’m a symmetry freak! I find that Xcode doesn’t handle text indentation well when you use dispatch_async(), so purely visually the code is not so pleasant to look at.

You might have other preferences, so yes – if you prefer dispatch_async(dispatch_get_main_queue(), ^(){…}); go for it!

So, when the data has arrived the method fetchedData: will be called and the NSData instance will be passed to it. In our case the JSON file is relatively small so we’re going to do the parsing inside fetchedData: on the main thread. If you’re parsing large JSON feeds (which is often the case), be sure to do that in the background.

So next add the fetchedData method to the file:


- (void)fetchedData:(NSData *)responseData {
//parse out the json data
NSError* error;
NSDictionary* json = [NSJSONSerialization
JSONObjectWithData:responseData //1

options:kNilOptions
error:&error];

NSArray* latestLoans = [json objectForKey:@"loans"]; //2

NSLog(@"loans: %@", latestLoans); //3
}


This is it – the new iOS 5 JSON magic!

Basically iOS 5 has a new class named NSJSONSerialization. It has a static method called JSONObjectWithData:options:error that takes an NSData and gives you back a Foundation object – usually an NSDictionary or an NSArray depending what do you have at the top of your JSON file hierarchy.

In Kiva.org’s case at the top there’s a dictionary, which has a key with list of loans. In line 1, we get an NSDictionary from the JSON data. In line 2, we get an NSArray latestLoans which is the loans key in the top JSON dictionary.

Finally in line 3, we dump latestLoans to the console, so we’re sure everything’s OK. Hit Run and check Xcode’s console to see the result:

descr

Not bad for 3 lines of code, eh? :]

Parsing Options


I’d like to talk just a bit more about NSJSONSerialization’s JSONObjectWithData:options:error: method. It’s one of these Apple APIs which understand and do everything by themselves, but you still can configure a bit its behavior.

Notice in the code that I chose to pass for the parameter options a value of kNilOptions. kNilOptions is just a constant for 0 – I find its name very descriptive though, so I always prefer it over just the value of 0 as a method parameter.

However you can pass other values or even a bit mask of values to combine them. Have a look at what you got as options when you’re converting JSON to objects:

  • NSJSONReadingMutableContainers: The arrays and dictionaries created will be mutable. Good if you want to add things to the containers after parsing it.
  • NSJSONReadingMutableLeaves: The leaves (i.e. the values inside the arrays and dictionaries) will be mutable. Good if you want to modify the strings read in, etc.
  • NSJSONReadingAllowFragments: Parses out the top-level objects that are not arrays or dictionaries.

So, if you’re not only reading, but also modifying the data structure from your JSON file, pass the appropriate options from the list above to JSONObjectWithData:options:error:.

Displaying to the Screen


We’re going to continue by showing the latest loan information on the screen. At the end of “fetchedData:” method add these few lines of code:


// 1) Get the latest loan
NSDictionary* loan = [latestLoans objectAtIndex:0];

// 2) Get the funded amount and loan amount
NSNumber* fundedAmount = [loan objectForKey:@"funded_amount"];
NSNumber* loanAmount = [loan objectForKey:@"loan_amount"];
float outstandingAmount = [loanAmount floatValue] -
[fundedAmount floatValue];

// 3) Set the label appropriately
humanReadble.text = [NSString stringWithFormat:@"Latest loan: %@
from %@ needs another $%.2f to pursue their entrepreneural dream",
[loan objectForKey:@"name"],
[(NSDictionary*)[loan objectForKey:@"location"]
objectForKey:@"country"],
outstandingAmount];


The latestLoans array is a list of dictionaries, so (1) we get the first (and latest) loan dictionary and (2) we fetch few values about the loan. Finally (3) we set the text of the 1st label in the UI.

OK! Let’s have a look – hit Run and see what comes up:

descr

Of course the information you see will be different as Kiva adds loans constantly – but it’s clear we achieved what we wanted, we parsed JSON data and visualized some human readable info.

Generating JSON Data


Now let’s do the opposite. From the loan NSDictionary that we now have we’ll build some JSON data, which we will be able to send over to a server, another app, or do with it whatever else we want.

Add this code to the end of the fetchedData: method:


//build an info object and convert to json
NSDictionary* info = [NSDictionary dictionaryWithObjectsAndKeys:
[loan objectForKey:@"name"],
@"who",
[(NSDictionary*)[loan objectForKey:@"location"]
objectForKey:@"country"],
@"where",
[NSNumber numberWithFloat: outstandingAmount],
@"what",
nil];

//convert object to data
NSData* jsonData = [NSJSONSerialization dataWithJSONObject:info
options:NSJSONWritingPrettyPrinted error:&error];


Here we build an NSDictionary called info where we store the loan information as who, where, and what in different keys and values.

Then we call dataWithJSONObject:options:error: – the opposite to the JSON API we just used before. It takes in an object and turns it into JSON data.

For the options parameter there’s only one possible value – NSJSONWritingPrettyPrinted. If you want to send the JSON over the Internet to a server use kNilOptions as this will generate compact JSON code, and if you want to see the JSON use NSJSONWritingPrettyPrinted as this will format it nicely.

So, at this point our job of turning info into JSON is finished, but we can’t be sure before we see that it is actually so. Let’s show the JSON into our second UI label. Add this final line of code to fetchedData:


//print out the data contents
jsonSummary.text = [[NSString alloc] initWithData:jsonData                                       
encoding:NSUTF8StringEncoding];


By initializing an NSString with initWithData:encoding: we easily get the text representation of our JSON data and we show it straight inside the jsonSummary label. Hit Run and:

descr

Integrating Objects and JSON


Imagine if NSDictionary, NSArray, NSString, and NSData had methods to convert to and from JSON data – wouldn’t that be great?

Oh, but wait – we’re using it’s Objective-C, so we can actually extend foundation classes with methods of our own! Let’s do an example with NSDictionary and see how useful that could be.

Open ViewController.m, and add this category just above the @implementation:


@interface NSDictionary(JSONCategories)
+(NSDictionary*)dictionaryWithContentsOfJSONURLString:
(NSString*)urlAddress;
-(NSData*)toJSON;
@end

@implementation NSDictionary(JSONCategories)
+(NSDictionary*)dictionaryWithContentsOfJSONURLString:
(NSString*)urlAddress
{
NSData* data = [NSData dataWithContentsOfURL:
[NSURL URLWithString: urlAddress] ];
__autoreleasing NSError* error = nil;
id result = [NSJSONSerialization JSONObjectWithData:data
options:kNilOptions error:&error];
if (error != nil) return nil;
return result;
}

-(NSData*)toJSON
{
NSError* error = nil;
id result = [NSJSONSerialization dataWithJSONObject:self
options:kNilOptions error:&error];
if (error != nil) return nil;
return result;   
}
@end


As there’s nothing new that I didn’t speak about so far in this tutorial I won’t go over the code line by line.

But basically, we define 2 methods on NSDictionary: one dictionaryWithContentsOfJSONURLString: which gets an NSString with a web address (it’s often easier to work with URLs as text, not as NSURL instances), does all the downloading, fetching, parsing and whatnot and finally just returns an instance of a dictionary (or nil in case of an error) – ain’t that pretty handy?

In the category there’s also one more method – toJSON which you call on an NSDictionary instance to get JSON data out of it.

So with this category fetching JSON from the web becomes as easy as :


NSDictionary* myInfo =
[NSDictionary dictionaryWithContentsOfJSONURLString:
@"http://www.yahoo.com/news.json"];


And of course on any of your NSDictionary objects you can do:


NSDictionary* information =
[NSDictionary dictionaryWithObjectsAndKeys:
@"orange",@"apple",@"banana",@"fig",nil];
NSData* json = [information toJSON];


Pretty cool and readable code. Now of course you can also extend NSMutableDictionary with the same dictionaryWithContentsOfJSONURLString: method, but in there you’ll have to pass NSJSONReadingMutableContainers as options – so hey, NSMutableDictionary could be initialized with JSON too, and it’ll hold mutable data. Cool!

Where to Go From Here?


Here is a example project with all of the code from the above tutorial.

At this point, you have hands-on experience with the awesome new iOS5 JSON reading and writing APIs, and are ready to start using this in your own apps!

Before we go though, I want to mention just few more methods from the NSJSONSerialization class.


BOOL isTurnableToJSON = [NSJSONSerialization
isValidJSONObject: object]


As you might guess, isValidJSONObject: tells you whether you can successfully turn a Cocoa object into JSON data.

Also I presented to you the 2 methods to read and write JSON from/to NSData objects, but you can do that also on streams – with JSONObjectWithStream:options:error: and writeJSONObject:toStream:options:error:, so do have a look at the class documentation.

If you want to keep playing around with JSON, feel free to extend the demo project with the following features:

  • Modify the demo project to use the JSON categories, like we discussed above

    Develop further JSON categories for NSArray, NSString, etc
  • Think about how cool it’d be if your classes had a toJSON method – so you can easily persist them on your web server!
  • Make an implementation on a test class to see if you can get it working!

This is an example of one of the “bonus” chapters from our new book iOS 5 By Tutorials. The bonus chapters like this one cover some of the cool but easily overlooked new APIs in iOS 5 like the new Address Book APIs, new Location APIs, new Calendar APIs, and much more, so if you want to learn more about iOS 5 check it out!

If you have any questions, comments, or suggestions, please join in the forum discussion below!

This is a blog post by iOS Tutorial Team member Marin Todorov, a software developer with 12+ years of experience, an independant iOS developer and the creator of Touch Code Magazine.

Working with JSON in iOS 5 Tutorial is a post from: Ray Wenderlich

没有评论:

发表评论