Tải bản đầy đủ (.pdf) (57 trang)

more iphone 3 development phần 7 ppt

Bạn đang xem bản rút gọn của tài liệu. Xem và tải ngay bản đầy đủ của tài liệu tại đây (10.3 MB, 57 trang )

CHAPTER 9: Online Play: Bonjour and Network Streams
326
@"Unable to publish Bonjour service(%@/%@)"), errorDomain, errorCode] ];

[theNetService stop];
}

- (void)netServiceDidStop:(NSNetService *)netService {
self.netService.delegate = nil;
self.netService = nil;
}
Next up is an NSNetService delegate that is called whenever an error is encountered.
This is called if an error is encountered either with publishing a service or resolving one.
All we do is show an alert.
#pragma mark -
#pragma mark Net Service Delegate Methods (General)
- (void)handleError:(NSNumber *)error withService:(NSNetService *)service {
[self showErrorAlertWithTitle:NSLocalizedString(@"A network error occurred.",
@"A network error occurred.") message:[NSString stringWithFormat:
NSLocalizedString(
@"An error occurred with service %@.%@.%@, error code = %@",
@"An error occurred with service %@.%@.%@, error code = %@"),
[service name], [service type], [service domain], error]];
}
There are two delegate methods related to resolving discovered services: one is called if
the service could not be resolved, and one is called if it resolves successfully. If it fails to
resolve, we just show an alert and stop trying to resolve the service.
#pragma mark -
#pragma mark Net Service Delegate Methods (Resolving)
- (void)netService:(NSNetService *)sender didNotResolve:(NSDictionary *)errorDict {


NSNumber *errorDomain = [errorDict valueForKey:NSNetServicesErrorDomain];
NSNumber *errorCode = [errorDict valueForKey:NSNetServicesErrorCode];
[self showErrorAlertWithTitle:NSLocalizedString(@"Unable to connect",
@"Unable to connect") message:[NSString stringWithFormat:
NSLocalizedString(@"Could not start game with remote device (%@/%@)",
@"Could not start game with remote device (%@/%@)"), errorDomain,
errorCode] ];
[sender stop];
}
If it resolved successfully, then we stop listening for new connections and get the stream
pair for the connection. If we’re not able to get the stream pair, we show an error alert;
otherwise, we create an OnlineSession object with the stream pair.
- (void)netServiceDidResolveAddress:(NSNetService *)service {

[self.onlineSessionListener stopListening];
self.onlineSessionListener = nil;

NSInputStream *tempIn = nil;
NSOutputStream *tempOut = nil;
if (![service getInputStream:&tempIn outputStream:&tempOut]){
[self showErrorAlertWithTitle:NSLocalizedString(@"Unable to connect",
@"Unable to connect") message:NSLocalizedString(
@"Could not start game with remote device",
CHAPTER 9: Online Play: Bonjour and Network Streams
327
@"Could not start game with remote device") ];
return;
}

OnlineSession *theSession = [[OnlineSession alloc]

initWithInputStream:tempIn outputStream:tempOut];
theSession.delegate = self;
self.onlineSession = theSession;
[theSession release];
}
When an OnlineListener detects a connection, it notifies its delegate. In that case, we
also create an OnlineSession object with the stream pair we got from the listener.
#pragma mark -
#pragma mark Online Session Listener Delegate Methods
- (void) acceptedConnectionForListener:(OnlineListener *)theListener
inputStream:(NSInputStream *)theInputStream
outputStream:(NSOutputStream *)theOutputStream {
OnlineSession *theSession = [[OnlineSession alloc]
initWithInputStream:theInputStream outputStream:theOutputStream];
theSession.delegate = self;
self.onlineSession = theSession;

[theSession release];
}
Our OnlineSession object, regardless of whether it was created by resolving a service or
by accepting a connection from another machine, will call onlineSessionReadyForUse:
when both streams are open. In this method, we check to see if we’re still presenting a
modal view controller, which would be the case if we received a connection from
another machine; if so, we dismiss it. Then we start a new game.
#pragma mark -
#pragma mark Online Session Delegate Methods
- (void)onlineSessionReadyForUse:(OnlineSession *)session {
if (self.modalViewController)
[self dismissModalViewControllerAnimated:YES];


[self startNewGame];
}
When we receive data from the OnlineSession, all we need to do is pass that on to the
handleReceivedData: method.
- (void)onlineSession:(OnlineSession *)session receivedData:(NSData *)data {
[self handleReceivedData:data];
}
If any of the three OnlineSessionDelegate error methods are called, we throw up an
error alert and kill the session.
- (void)onlineSession:(OnlineSession *)session
encounteredReadError:(NSError *)error {
[self showErrorAlertWithTitle:NSLocalizedString(@"Error reading",
@"Error Reading") message:NSLocalizedString(@"Could not read sent packet",
@"Could not read sent packet")];
self.onlineSession = nil;
CHAPTER 9: Online Play: Bonjour and Network Streams
328
}
- (void)onlineSession:(OnlineSession *)session
encounteredWriteError:(NSError *)error {
[self showErrorAlertWithTitle:NSLocalizedString(@"Error Writing",
@"Error Writing") message:NSLocalizedString(@"Could not send packet",
@"Could not send packet")];
self.onlineSession = nil;
}
- (void)onlineSessionDisconnected:(OnlineSession *)session {
[self showErrorAlertWithTitle:NSLocalizedString(@"Peer Disconnected",
@"Peer Disconnected") message:NSLocalizedString(
@"Your opponent disconnected or otherwise could not be reached.",
@"Your opponent disconnected or otherwise could not be reached")];

self.onlineSession = nil;
}
@end
WHAT ABOUT INTERNET PLAY?
If you want to offer play over the Internet, the process is almost exactly the same. You still need to listen on a
port, and you still use streams to exchange data with the remote machine. Generally speaking, you do not use
Bonjour to advertise services over the Internet, though. Typically, a dedicated server will be used to find
opponents or, more rarely, users will be asked to type in the address and port to which they want to connect.
To find out more about getting a stream connection to a remote machine based on DNS name or IP
address and port, you should read Tech Note QA1652, which is available at

Time to Play
And with that marathon of changes, we have now implemented online play in our
TicTacToe application. You can select Build and Run from the Build menu to try it out.
About time, huh?
Online play is significantly more complex to implement than GameKit over Bluetooth,
but there’s good news. The OnlineSession and OnlineListener objects we just wrote
are completely generic. Copy them to a new project, and you can use them unchanged.
That means your next application that needs to support network play will be almost as
easy to write as it would be to use GameKit.
Before we leave the topic of networking completely, we have one more chapter of
network goodness for you. We’re going to show you a variety of ways to retrieve
information from web servers and RESTful web services.


329
329
Chapter
Working with Data from
the Web

As you saw in the last chapter, writing code to communicate over a network can be
complex and, at times, difficult. Fortunately, for many common network-related tasks,
Apple has provided higher-level methods and objects that will make your life
considerably easier. One fairly common task when you’re writing software for a device
that’s pretty much always connected to the Internet is to retrieve data from web servers.
There is a large amount of data available for applications to use on the World Wide Web,
and there are countless reasons why an iPhone application might want to pull data from
the Web.
NOTE: The applications we’re writing in this chapter will work just fine on the simulator. But, as
you might expect, since those applications will be retrieving data from the Web, they’ll only work
if the computer on which the simulator is running has an active connection to the Internet.
There are a number of techniques you can use to grab data from web servers. In this
chapter, we’re going to show you three of them. We’ll first show you how to leverage
special methods that exist in several Foundation classes that allow you to retrieve data
based on a URL in just a line or two of code. We’ll expand on that and show you how to
take more control over the process so that you can detect when errors occur. Next, we’ll
show you how to pull data asynchronously, so your application can do other things
while data is being retrieved in the background. And finally, we’ll learn how to make
different types of HTTP requests and pass form parameters so you can retrieve data
from web applications and web services as well as static files.
Since each of these topics stands alone, we’ll build our chapter application-iteratively.
We’ll discuss one type of retrieval, then add it to the application.
We’ll start by setting up an application skeleton. Next, we’ll add URL-based methods to
retrieve both an image and text from the Web. Then we’ll talk about doing a more robust
form of data retrieval, and then add code to our application to retrieve the same image
10

CHAPTER 10: Working with Data from the Web
330
and text file using that approach. After that, we’ll talk about asynchronous data retrieval

and then add code to our application to retrieve the text and image in the background.
You can look at Figure 10–1 to see what our application will look like when done.

Figure 10–1. One of the two applications we’ll build in this chapter The top row of buttons will retrieve an image
file from a web server in one of three different ways. The bottom row of buttons will retrieve a text document in
one of three different ways.
Once we’re done with those different ways of retrieving static data, we’ll move on to
forms and various HTTP request types. Then we will build another small application that
uses both kinds of form parameters and two different request types (Figure 10–2).
CHAPTER 10: Working with Data from the Web
331

Figure 10–2. The second application we’re going to build in this chapter shows how to change the request type
and how to pass form parameters
Setting Up the Application Skeleton
We’re going to start by creating an application skeleton with stub methods for each of
the tasks that we’re going to implement in the first application. A stub method
(sometimes referred to as just a stub) is typically an empty method, or one with only one
or two lines of code designed to act as a placeholder for a method that you plan to
add later. This allows you to set up your user interface before you’re ready to write the
code behind it. As we discuss the different ways to retrieve data, we will add code to
these stubs.
In Xcode, create a new project, select the View-based Application template, and call the
new project WebWork. Once the project is open, find the project archives that
accompany this book and look in the 10 – WebWork folder for the images called
blue_get.png, green_get.png, lavender_get.png, text.png, and image.png and add them
all to your project. These are the images you’ll need for the buttons as well as the text
and image icons that appear to the left of the buttons in Figure 10–1.
Declaring Actions and Outlets
Single-click on WebWorkViewController.h so we can add our outlet and action

declarations. Replace the existing contents with the following code:
CHAPTER 10: Working with Data from the Web
332
#import <UIKit/UIKit.h>

#define kImageURL @"
#define kTextURL @"

typedef enum RequestTypes {
kRequestTypeImage,
kRequestTypeText,
} RequestType;

@interface WebWorkViewController : UIViewController {
UIActivityIndicatorView *spinner;
UIImageView *imageView;
UITextView *textView;

NSMutableData *receivedData;
RequestType requestType;
}

@property (nonatomic, retain) IBOutlet UIActivityIndicatorView *spinner;
@property (nonatomic, retain) IBOutlet UIImageView *imageView;
@property (nonatomic, retain) IBOutlet UITextView *textView;
@property (nonatomic, retain) NSMutableData *receivedData;

- (void)clear;

- (IBAction)getImageUsingNSData;

- (IBAction)getImageSynchronously;
- (IBAction)getImageAsynchronously;

- (IBAction)getTextUsingNSString;
- (IBAction)getTextSynchronously;
- (IBAction)getTextAsynchronously;
@end
We start off by defining two constants that point to an image file and a text file that
we’ve hosted on the Internet for your use. This is the data that we’ll be pulling into our
application. Feel free to use different URLs if you prefer.
#define kImageURL @"
#define kTextURL @"
Next, we define a new type along with an enum. In some parts of our code, we will be
using delegate methods (surprise!), and we will need a way to know in one of those
delegate methods whether the data being we’re retrieving holds an image or text. While
there are ways to determine that from the web server’s response (which we’ll see later in
the chapter), just keeping track of which we’ve requested is a lot easier and more
efficient.
typedef enum RequestTypes {
kRequestTypeImage,
kRequestTypeText,
} RequestType;
We have three views that we’ll need outlets to so that we can show the returned data.
The UIImageView will be used to show the retrieved image, the UITextView will be used
CHAPTER 10: Working with Data from the Web
333
to display the retrieved text, and the UIActivityIndicatorView is that white spinning
doohickey that tells the user that some action is in progress (you’ll know it when you see
it). When we retrieve the data asynchronously, we’ll show the activity indicator so that
the user knows we’re in the process of retrieving the data they requested. Once we have

the data, we’ll hide the activity indicator and show the image or text that was requested.
@interface WebWorkViewController : UIViewController {
UIActivityIndicatorView *spinner;
UIImageView *imageView;
UITextView *textView;
We also declare an instance of NSMutableData that will be used to store the data when
fetching asynchronously. When we do that, a delegate method that we will implement
will be called repeatedly and provided with small chunks of the requested data. We will
accumulate those chunks in this instance so that when the process is complete, we’ll
have the whole image or text file.
NSMutableData *receivedData;
And, here’s where we’ll keep track of whether an image or text was last requested.
RequestType requestType;
We also declare properties for our instance variables, using the IBOutlet keyword for
those that will need to be connected to objects in Interface Builder.
@property (nonatomic, retain) IBOutlet UIActivityIndicatorView *spinner;
@property (nonatomic, retain) IBOutlet UIImageView *imageView;
@property (nonatomic, retain) IBOutlet UITextView *textView;
@property (nonatomic, retain) NSMutableData *receivedData;
And then we have our methods. The first one is just used to clear the requested data so
that the application can be used again without restarting.
- (void)clear;
And we have six action methods, one for each of the buttons you can see in Figure 10–
1. Since each button represents a different way to retrieve one kind of data, it makes
sense to give each of the buttons its own action method.
- (IBAction)getImageUsingNSData;
- (IBAction)getImageSynchronously;
- (IBAction)getImageAsynchronously;

- (IBAction)getTextUsingNSString;

- (IBAction)getTextSynchronously;
- (IBAction)getTextAsynchronously;
Designing the Interface
Now that we have our actions and outlets in place, make sure you save first, then
double-click WebWorkViewController.xib to open up the file in Interface Builder.
Let’s start off by dragging an Image View from the library over to the window labeled
View. Interface Builder will resize the image view to take up the whole window, which
CHAPTER 10: Working with Data from the Web
334
isn’t what we want this time, so press 3 to bring up the size inspector, change the X
and Y value each to 20, set W to 280, and set H to 255.
Then, control-drag from File’s Owner to the image view and select the imageView outlet.
Press 1 and use the attribute inspector to change the Mode from Center to Aspect Fit
so that the image will be resized to fit.
Now, drag a Text View from the library to the View window. Place it in exactly the same
location as the image view and make it exactly the same size. Once it’s placed, control-
drag from File’s Owner to the text view and select the textView outlet. Double-click the
text view so that the text it contains is editable, make sure all the text is selected, and hit
the delete button. In the attribute inspector, uncheck the box that says Editable so that
our user can’t change the downloaded text.
In the library, look for an Activity Indicator View and drag one to the View window. Use
the blue guidelines to line it up with the horizontal and vertical centers of the text and
image views you already added. Then, control-drag from File’s Owner to the activity
indicator and select the spinner outlet. Press 1 to bring up the attribute inspector
and check Hide When Stopped so that when the indicator is not spinning, it won’t be
visible.
Now, drag another Image View to the view. Place it somewhere in the bottom half of the
screen; the exact placement doesn’t matter for now. Press 1and use the attribute
inspector to select the text.png for the Image field. Press = to resize the image view to
match the image, then place the resized image view in the lower-left of the window,

using the blue guidelines to place it against the bottom and left margins.
Bring over another Image View and select image.png for its image. Use = to resize the
image view and then place it above the image view you placed a moment ago, using
Figure 10–1 as a guide.
Next, bring over a Round Rect Button from the library, and use the size inspector (3)
to change both the height and width of the button to 57 pixels. Place the button to the
right of the image.png image view. Now, use the attributes inspector to change the
button’s type from Rounded Rect to Custom and select blue_get.png from the Image
pop-up. Option-drag the button to the right to create a second one,
then repeat to
create a third button. Change the image of the second button to green_get.png and
change the image of the third button to lavender_get.png. Finally, select all three buttons
and option-drag them to create three new buttons below the first set of buttons. Use
Figure 10–1 as a guide to help you place everything just so.
Now, bring over a Label over from the library, and place it above the left-most button,
the blue one. Change the font size to 14 points (you can change the font size using the
fonts palette T) and change the text to Object. Now option-drag the label to create a
second and third copy, placing one above the second and third column of buttons.
Change the second label to read Sync, and the third label to read Async. Again, use
Figure 10–1 as a guide.
Now, control-drag from all six of the buttons to File’s Owner and select the action
methods that match the button’s position. For the top-left button, for example, you
CHAPTER 10: Working with Data from the Web
335
should select getImageUsingNSData, and for the bottom-left button you should select
getTextUsingNSString. Once you have connected all six buttons to the appropriate
action method, save the nib and head back to Xcode.
Implementing the Stubs
Now we’re going to write our implementation file, but aren’t going to write any of the
actual code to retrieve the data yet. We’re just putting in placeholders so we have a

place to add the code later in the chapter. Single-click WebWorkViewController.m and
replace the current contents with the following:
#import "WebWorkViewController.h"

@implementation WebWorkViewController
@synthesize spinner;
@synthesize imageView;
@synthesize textView;
@synthesize receivedData;

- (void)clear {
imageView.hidden = YES;
textView.hidden = YES;
}

- (IBAction)getImageUsingNSData {
NSLog(@"Entering %s", __FUNCTION__);
}

- (IBAction)getImageSynchronously {
NSLog(@"Entering %s", __FUNCTION__);
}

- (IBAction)getImageAsynchronously {
NSLog(@"Entering %s", __FUNCTION__);
}

- (IBAction)getTextUsingNSString {
NSLog(@"Entering %s", __FUNCTION__);
}


- (IBAction)getTextSynchronously {
NSLog(@"Entering %s", __FUNCTION__);
}

- (IBAction)getTextAsynchronously {
NSLog(@"Entering %s", __FUNCTION__);
}

- (void)viewDidUnload {
self.spinner = nil;
self.imageView = nil;
self.textView = nil;
}

- (void)dealloc {
CHAPTER 10: Working with Data from the Web
336
[spinner release];
[imageView release];
[textView release];
[receivedData release];
[super dealloc];
}

@end
The only thing in this file right now that might be new to you are the lines that look like
this:
NSLog(@"Entering %s", __FUNCTION__);
All this line does is print to the console the name of the method that’s being called.

__FUNCTION__ is a special macro that compiles into a C-string that holds the name of the
function or method currently being executed.
NOTE: It may not be obvious from looking at it on the printed page, but __FUNCTION__ has two
underscores at the beginning and another two underscores at the end for a total of four
underscore characters.
By doing this, we can quickly check our stubs to make sure they get called when they’re
supposed to be. Save and then select Build and Run from the Build menu. You should be
able to click all six of the buttons and have the appropriate method for each button print
in the console. This is a good way, when building your own applications, to make sure
that your nib is set up correctly. A missed nib connection can be surprisingly difficult to
debug, so making sure all your connections are made and are made to the correct
actions before you start writing application code can be a very good idea (Figure 10–3).

Figure 10–3. With these stubs in place, you can quickly check out your Interface Builder action connections to
make sure every button triggers the right method.
Retrieving Data Using Foundation Objects
By far, the easiest way to retrieve data from a web server is to use a class that has an
init method or factory method whose name contains withContentsOfURL:. These are
CHAPTER 10: Working with Data from the Web
337
special methods that take care of all aspects of retrieving a particular kind of data from
the Internet. All you have to do is provide these methods with an instance of NSURL, a
class that holds a single URL, and it will initialize and return an object containing the
data pointed to by the URL.
NOTE: These URL-based methods can also be used to create objects based on data located in a
local file or using other Internet protocols like FTP. Basically, any data that can be retrieved using
a URL can be used to instantiate these objects.
To initialize an NSData instance from a file on the Web, for example, you could do this:
NSString *theUrlString = @"
NSURL *url = [NSURL urlWithString:theUrlString];

NSData *imageData = [NSData dataWithContentsOfURL:url];
To initialize an NSString instance from a file on the Web, it looks like this:
NSString *theUrlString = @"
NSURL *url = [NSURL urlWithString:theUrlString];
NSString *string = [NSString stringWithContentsOfURL:url
encoding:NSUTF8StringEncoding error:nil];
These aren’t the only two classes that have URL-based init or factory methods, but they
are the two that you will most commonly use. Most of the other methods only work if the
provided URL points to data of a specific type or that’s in a specific format. For
NSDictionary and NSArray, for example, the URL has to lead to a property list in the
format that those classes need. For AVAudioPlayer, the URL must point to a valid audio
file in a format that the iPhone supports natively. How you use all of these objects is
identical to the two examples shown in this section, however.
There’s no doubt that these methods are convenient. If you compare the three-line
process in these examples with the process we went through in the previous chapter to
receive data from another device, these methods must seem laughably easy. But they
do have some drawbacks. In fact, there are two major drawbacks to this approach that
prevent them from being used in a lot of places. First, if anything goes wrong, the only
indication you get is that they return nil. You aren’t told if the file doesn’t exist, or if the
network connection is down. You just get a nil, and you should be happy about it. Why,
when we were kids, we’d walk 20 miles, barefoot, in a blizzard just for a chance to see a
nil. And we liked it!
Okay, a few of these methods will return an NSError object using a pointer to a pointer,
as you can see in the last line of the NSString example, so in some instances, you have
a little bit more information than just a nil, but with these techniques, you do not get
detailed information about how the server responded.
The other drawback is that the process is synchronous, which means that when you call
the method, no other code can run (at least on the main thread that controls the user
interface) until it has finished downloading the data. If you’re pulling down a small text
file, that might not be a big deal, but if you’re pulling down a high-res image or a video

CHAPTER 10: Working with Data from the Web
338
file, it’s a very big deal. Your user interface will become unresponsive and your
application will be unable to do anything else until the data has all been retrieved.
As a result, you should limit your use of these methods for retrieving data from the
network to very small pieces of data, and even then, use them with caution. Users do
not appreciate apps that become unresponsive for no apparent reason, and this as a
reason will definitely not be apparent to most end users. They also don’t like when
things don’t work and they don’t know why. If they are expecting an image, and you give
them nothing and no explanation about why they’re getting nothing, they’re bound to be
unhappy about it.
Let’s implement the two left-most buttons in our application so you can see this process
in action.
Single-click WebWorkViewController.m and replace the existing stub implementation of
getImageUsingNSData with this new version that retrieves a picture from the Web using
NSData:
- (IBAction)getImageUsingNSData {
textView.hidden = YES;
imageView.hidden = NO;

NSURL *url = [NSURL URLWithString:kImageURL];
NSData *imageData = [NSData dataWithContentsOfURL:url];
imageView.image = [UIImage imageWithData:imageData];
[self performSelector:@selector(clear) withObject:nil afterDelay:5.0];
}
Also replace the existing stub implementation of getTextUsingNSString with this new
version:
- (IBAction)getTextUsingNSString {
textView.hidden = NO;
imageView.hidden = YES;

NSURL *url = [NSURL URLWithString:kTextURL];
textView.text = [NSString stringWithContentsOfURL:url
encoding:NSUTF8StringEncoding error:nil];
[self performSelector:@selector(clear) withObject:nil afterDelay:5.0];
}
In each of these methods, we make sure the appropriate view for the type of data we’re
using is visible, then create an NSURL instance based on one of the two string constants
we declared earlier. Then we retrieve the data from the Web using those special
methods and stick the data into the appropriate view. Once we’re all done with that, we
use performSelector:withObject:afterDelay: to clear the text or image after five
seconds so the user can try another button without having to quit.
Now try it out. When you use the top-left button, you should end up with a picture of the
cover of this book, like the one shown in Figure 10–1. If you tap the lower-left button,
you’ll get the first page of the Iliad by Homer (Figure 10–4). Since the image and text
being retrieved here are relatively small, you probably won’t notice more than a minor
hiccup in your application’s reponsiveness after you tap the button. If you’re on a fast
enough connection, you may not even notice that. But, trust us when we say that if you
were to do this to retrieve a large data file, the delay would definitely be noticeable.
CHAPTER 10: Working with Data from the Web
339

Figure 10–4. The bottom row of buttons will retrieve the first page of the Iliad from a web server
Retrieving Data Synchronously
The code we just added was short and sweet, and it did the job. Mostly. But what if
there was a problem? What if the file wasn’t found, or the server wasn’t responding?
What if the user’s Internet connection was down for some reason? The URL init or
factory methods would return nil, and all we’d know for sure was that something
prevented the object from being created. In most cases, we’re going to want more
information than that. We’re going to want to know why our call failed so we can give
our users a satisfying answer about what went wrong.

The URL Request
To do that, we have to take a little more control over the situation. Instead of using an
init or factory method that takes an NSURL, we have to create an object called an
NSURLRequest (or just a “request”), which is used to request data from a remote server
using a URL. Here’s how we create such a request:
NSURLRequest *req = [[NSURLRequest alloc] initWithURL:url];
Not too difficult, huh? Okay, so once you have your request, how do you use it to get
data? In addition to the request, we also need a connection, which is represented by the
class NSURLConnection. To request data synchronously, however, we don’t actually
CHAPTER 10: Working with Data from the Web
340
have to create a connection, we can just use a class method on NSURLConnection to
send our request and retrieve the data, like so:
NSHTTPURLResponse* response = nil;
NSError* error nil;
NSData *responseData = [NSURLConnection sendSynchronousRequest:req
returningResponse:&response
error:&error];
As Newton said, every request has an equal and opposite response. Okay, we didn’t
really pay much attention in Physics class, so that’s probably not quite what he said, but
it’s true in the context of the Web. For every request you send, you get back a response.
The NSHTTPURLResponse object holds the response from the server if it was able to reach
the server, or nil if the server could not be reached. On return, the response object will
contain all the information provided by the server in response to that request except for
the actual data from the requested file which, in the this example, is held in
responseData.
That response object gives us much more information than our previous examples
because it tells us exactly what happened. It will contain a response code, which tells
us if the server was able to fulfill the request, and how. It also contains a content-type
which tells us what kind of data is contained in responseData. We can retrieve the

content type and response code like this:
NSInteger statusCode = [response statusCode];
NSString *contentType = [[response allHeaderFields]
objectForKey:@"Content-Type"];
TIP You can find a list of the HTTP response codes and response header fields in the HTTP
protocol specification at />sec6.html. The IANA (the same organization that keeps the port number registery we talked
about in the last chapter) also keep a registry of content-types, which you can find at

As we said before, if the server couldn’t be reached at all, then response will be nil. If
the server responded, but something went wrong, the response code will give us more
information about the problem. If reponseData is nil, we might find out that the data
wasn’t found (response code 404) or that it moved to a new location (301) or that we
don’t have privileges to download it (401). Armed with the list of response codes, we can
give our users a much better answer about why we weren’t able to get the file for them.
We can also ensure that the data we’re receiving is the same type that we were
expecting. Web servers will often forward requests, so responseData might contain, for
example, the HTML for a 404 page, or a page full of ads rather than the file we were
trying to retrieve.
Let’s use this technique to implement the middle two buttons of our application. Single-
click WebWorkViewController.m if it’s not already selected and replace the existing stub
implementation of getImageSynchronously with the following version:
CHAPTER 10: Working with Data from the Web
341
- (IBAction)getImageSynchronously {
textView.hidden = YES;
imageView.hidden = NO;
NSURL *url = [[NSURL alloc] initWithString:kImageURL];
NSURLRequest *req = [[NSURLRequest alloc] initWithURL:url];

NSHTTPURLResponse* response = nil;

NSError* error = nil;
NSData *responseData = [NSURLConnection sendSynchronousRequest:req
returningResponse:&response
error:&error];
if (response == nil) {
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Error!"
message:@"Unable to contact server."
delegate:nil
cancelButtonTitle:@"Bummer"
otherButtonTitles:nil];
[alert show];
[alert release];
}

NSInteger statusCode = [response statusCode];
NSString *contentType = [[response allHeaderFields]
objectForKey:@"Content-Type"];

if (statusCode >= 200 && statusCode < 300 && [contentType hasPrefix:@"image"]) {
imageView.image = [UIImage imageWithData:responseData];
}
else {
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Error!"
message:[NSString stringWithFormat:
@"Encountered %d error while loading", statusCode]
delegate:nil
cancelButtonTitle:@"Bummer"
otherButtonTitles:nil];
[alert show];
[alert release];

}

[url release];
[req release];
[self performSelector:@selector(clear) withObject:nil afterDelay:5.0];
}
Now, find the getTextSynchronously stub and replace it with this version:
- (IBAction)getTextSynchronously {
textView.hidden = NO;
imageView.hidden = YES;
NSURL *url = [[NSURL alloc] initWithString:kTextURL];
NSURLRequest *req = [[NSURLRequest alloc] initWithURL:url];

NSHTTPURLResponse* response = nil;
NSError* error = nil;
NSData *responseData = [NSURLConnection sendSynchronousRequest:req
returningResponse:&response
error:&error];
if (response == nil) {
CHAPTER 10: Working with Data from the Web
342
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Error!"
message:@"Unable to contact server."
delegate:nil
cancelButtonTitle:@"Bummer"
otherButtonTitles:nil];
[alert show];
[alert release];
return;
}


NSInteger statusCode = [response statusCode];
NSString *contentType = [[response allHeaderFields]
objectForKey:@"Content-Type"];

if (statusCode >= 200 && statusCode < 300 && [contentType hasPrefix:@"text"]) {
NSString *payloadAsString = [[NSString alloc] initWithData:responseData
encoding:NSUTF8StringEncoding];
textView.text = payloadAsString;
[payloadAsString release];
}
else {
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Error!"
message:[NSString stringWithFormat:
@"Encountered %d error while loading", statusCode]
delegate:nil
cancelButtonTitle:@"Bummer"
otherButtonTitles:nil];
[alert show];
[alert release];
return;
}

[url release];
[req release];
[self performSelector:@selector(clear) withObject:nil afterDelay:5.0];
}
In both cases, we create an NSURL and an NSURLRequest, then use NSURLConnection to
send the request to the server. If the response is nil, we put up an alert telling our user
that the server could not be reached.

If response was not nil, then we check the response code and content type. Generally
speaking, the 200 series of response codes (200 through 299) are used to indicate that
the server was able to fulfill our request, so if we got a response code in that range, and
the content-type matches the type of data we’re expecting, we add the text or image it
contains to the appropriate view. Otherwise, we show an alert letting the user know that
there was a problem. After we’re done, we release url and req so that we don’t leak
memory, and then use performSelector:withObject:afterDelay: to reset the user
interface after five seconds.
Try out the new version. If all is right with the world, you should notice no difference
between what the middle buttons do and the left buttons do. But, if something does go
wrong, we’re much better equipped to inform the user. In our simple example here, the
user will know if there’s something wrong with their Internet connection (Figure 10–5) or
if the URL we used was wrong (Figure 10–6). You can test this out if you’re using the
CHAPTER 10: Working with Data from the Web
343
simulator by turning Airport off or unplugging your Ethernet cable so that the remote
server can’t be reached. Another way you can test is to change the URL to point to an
object that doesn’t exist on the server, like so:
#define kImageURL @"
#define kTextURL @"

Figure 10–5. If the network connecton isn’t working, or the remote server can’t be reached, we’re able to tell the
user that
That is much better, but we still have that little hiccup when the user presses the button.
With synchronous requests, the entire user interface freezes for the length of time it
takes to retrieve the data. Not a huge deal here where we’re only pulling a few kilobytes
of data, but potentially a very big deal in many situations. Let’s look at how to fix that by
requesting the data asynchronously.
CHAPTER 10: Working with Data from the Web
344


Figure 10–6. If we are able to reach the server, but the URL doesn’t point to what we think it does, we’re also
able to report that back to our user or take action based on the error code that was received
Retrieving Data Asynchronously
In the last chapter, we discussed CFNetwork’s interaction with an application’s run loop
and the notifications your application will receive when a variety of events occur, such
as receiving data. Well, the URL loading system that we just used to load data
synchronously can also leverage the run loop in a similar fashion. This will allow us to
request the data pointed to by a URL, and then go about our merry way while the
request chugs away in the background. Once the data has been received, we can then
take appropriate action, and our user interface will never become unresponsive.
As you’ve already seen in previous chapters, asynchronous network communication can
be hard. It can be. But it doesn’t have to be. Apple’s URL loading system actually makes
it pretty easy to retrieve data asynchronously. We start off in a manner pretty similar to
the synchronous request. This time, we will create an instance of NSURL and
NSURLRequest, just like before, but we’ll also create an instance of NSURLConnection. Last
time, we just used a class method on that object to retrieve the data, but this time we’re
actually going to create an instance. Just by instantiating NSURLConnection, we actually
kick off the asynchronous fetch. That’s all we have to do. We do have to specify a
delegate when we create the connection so NSURLConnection knows what object to
notify when something happens. You will usually specify self to make your controller
class (or whatever class this code is part of) the delegate. Here’s an example that
creates a connection object:
CHAPTER 10: Working with Data from the Web
345
NSURLRequest *req = [[NSURLRequest alloc] initWithURL:[NSURL
URLWithString:kTextURL]];
NSURLConnection *con =[[NSURLConnection alloc] initWithRequest:req
delegate:self];
If we were able to create a connection, then we need to make sure we’ve got a place to

store the data as it comes in. The easiest way to do that is to use an instance of
NSMutableData, like the one we declared in our header file earlier.
if (con) {
NSMutableData *data = [[NSMutableData alloc] init];
self.receivedData = data;
[data release];
}
After that, we’re done until the delegate calls one of our methods. The only thing we
need to do is release the request, because we allocated it:
[req release];
Notice that we do not release the connection, however. If we released the connection, it
would be deallocated because it’s not currently retained by anything else. Don’t worry,
though, we won’t leak the memory. When the connection is all finished, it will call one of
our delegate methods, and we will have the chance to free up its memory at that time.
Let’s look at the delegate methods now.
NSURLConnection Delegate Methods
When the connection is established and a response has been received by the
NSURLConnection object, the NSURLConnection will call the method
connection:didReceiveResponse: on its delegate. At this point, we can check the
response code to make sure we’ve received a valid code, but that’s not always
necessary. Here’s why.
With asynchronous handling, you will be notified multiple times if a request gets
forwarded, which isn’t an uncommon occurrence when requesting data from web
servers. A redirect typically results in a 300 series response code, which is then followed
by another response a few moments later with a new code. This often happens, for
example, if a resource moves to a new location on the server.
If the connection fails to retrieve the requested data, the connection will call another
delegate method to inform you of that, so very often you don’t even need to check the
response code in this method unless you specifically need to know about things like
redirects.

As we stated, if a connection is forwarded, this delegate method may be called multiple
times for a single request. One thing you need to do here, as a result, is to reset the
mutable data instance’s length to 0, which removes any data that it’s currently holding.
You do not want to include the data from any of the earlier redirect responses in the
object. Here’s an example implementation of this delegate method:
- (void)connection:(NSURLConnection *)connection
didReceiveResponse:(NSURLResponse *)response {
CHAPTER 10: Working with Data from the Web
346
// check response code here if necessary
[receivedData setLength:0];
}
After a response is received, if there is data, it will be sent to the delegate using the
method connection:didReceiveData:. This method typically gets called multiple times,
and you must capture all the data sent in the order in which it was sent, to ensure that
you have received the complete object. Fortunately, all that usually entails is appending
the received data onto the instance of NSMutableData being used to accumulate the
data, like so:
- (void)connection:(NSURLConnection *)connection
didReceiveData:(NSData *)data {
[receivedData appendData:data];
}
If an error is encountered while trying to retrieve the requested object, the delegate
method connection:didFailWithError: gets called. Here’s a simple implementation of
that method that simply logs the error:
- (void)connection:(NSURLConnection *)connection
didFailWithError:(NSError *)error {
[connection release];
self.receivedData = nil;
NSLog(@"Error retrieving data for url %@, error was: %@",

[error localizedDescription], [[error userInfo]
objectForKey:NSErrorFailingURLStringKey]);
}
In real-world applications, you’ll typically want to take more significant action when a
connection fails, at the very least informing the user of the failure.
When all of the data that makes up the requested object has been retrieved, the
connection will call the delegate method connectionDidFinishLoading:. When this
method is called, the instance of NSMutableData in which we’ve been collecting the
received data should have the complete object, and you can do whatever is appropriate
with it. You also need to release the connection here so that you don’t leak the memory.
It’s also usually appropriate to release the mutable data instance that was used to
accumulate the data, once you’ve used the data, though that may not always be the
case. Here’s a simple example that creates an instance of UIImage based on the
received data and puts it into a UIImageView.
- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
imageView.image = [UIImage imageWithData:receivedData];
[connection release];
self.receivedData = nil;
}
Adding Asynchronous Retrieval to WebWorks
Here we come, rounding third based on our WebWorks application. We’re almost done.
Find the stub implementation of getImageAsynchronously and replace it with this version:
- (IBAction)getImageAsynchronously {
CHAPTER 10: Working with Data from the Web
347
[spinner startAnimating];

NSURLRequest *req = [[NSURLRequest alloc] initWithURL:
[NSURL URLWithString:kImageURL]];
NSURLConnection *con = [[NSURLConnection alloc] initWithRequest:req

delegate:self];
if (con) {
NSMutableData *data = [[NSMutableData alloc] init];
self.receivedData = data;
[data release];
requestType = kRequestTypeImage;
}
else {
UIAlertView *alert = [[UIAlertView alloc]
initWithTitle:@"Error"
message:@"Error connecting to remote server"
delegate:self
cancelButtonTitle:@"Bummer"
otherButtonTitles:nil];
[alert show];
[alert release];
}
[req release];
}
Now find the stub implementation of getTextAsynchronously and replace it with this
version:
- (IBAction)getTextAsynchronously {
[spinner startAnimating];

NSURLRequest *req = [[NSURLRequest alloc] initWithURL:
[NSURL URLWithString:kTextURL]];
NSURLConnection *con = [[NSURLConnection alloc] initWithRequest:req
delegate:self];
if (con) {
NSMutableData *data = [[NSMutableData alloc] init];

self.receivedData = data;
[data release];
requestType = kRequestTypeText;
}
else {
UIAlertView *alert = [[UIAlertView alloc]
initWithTitle:@"Error"
message:@"Error connecting to remote server"
delegate:self
cancelButtonTitle:@"Bummer"
otherButtonTitles:nil];
[alert show];
[alert release];
}
[req release];
}
In both methods, we follow the same basic logic. First, we create the URL and request,
then use those to create an instance of NSURLConnection, specifying self as the
delegate. We check to make sure the connection object is not nil, which would indicate
CHAPTER 10: Working with Data from the Web
348
that the server could not be reached, and if we have a valid connection, we allocate our
NSMutableData instance to hold the data we’re about to start receiving.
So now, the right-hand buttons kick off an asynchronous request and shows the activity
indicator. Since the retrieval will happen in the background, there shouldn’t be a hiccup
or any noticeable unresponsiveness in the app. Of course, it also won’t ever show the
image or text because we haven’t implemented our connection delegate methods. Let’s
do that now. At the end of the file, just above the @end declaration, add the following
methods:
#pragma mark -

#pragma mark NSURLConnection Callbacks
- (void)connection:(NSURLConnection *)connection
didReceiveResponse:(NSURLResponse *)response {
[receivedData setLength:0];
}

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

- (void)connection:(NSURLConnection *)connection
didFailWithError:(NSError *)error {
[connection release];
self.receivedData = nil;

UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Error"
message:[NSString stringWithFormat:
@"Connection failed! Error - %@ (URL: %@)",
[error localizedDescription],[[error userInfo]
objectForKey:NSErrorFailingURLStringKey]]
delegate:self
cancelButtonTitle:@"Bummer"
otherButtonTitles:nil];
[alert show];
[alert release];
[spinner stopAnimating];
}

- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
if (requestType == kRequestTypeImage) {

imageView.hidden = NO;
textView.hidden = YES;
imageView.image = [UIImage imageWithData:receivedData];
}
else {
imageView.hidden = YES;
textView.hidden = NO;
NSString *payloadAsString = [[NSString alloc] initWithData:receivedData
encoding:NSUTF8StringEncoding];
textView.text = payloadAsString;
[payloadAsString release];
}

[connection release];
self.receivedData = nil;
CHAPTER 10: Working with Data from the Web
349
[spinner stopAnimating];
[self performSelector:@selector(clear) withObject:nil afterDelay:5.0];
}
Let’s look at what we did. The first connection delegate method we implement gets
called whenever the connection gets a response from the server. Remember, we might
get more than one response if the server forwards our request, so we reset our mutable
data every time this gets called:
- (void)connection:(NSURLConnection *)connection
didReceiveResponse:(NSURLResponse *)response {
[receivedData setLength:0];
}
Every time the connection has a chunk of data for us, it will call the next method we
wrote, so we take the data and append it to our mutable data instance.

- (void)connection:(NSURLConnection *)connection
didReceiveData:(NSData *)data {
[receivedData appendData:data];
}
In the event of an error, the connection will call our delegate method
connection:didFailWithError:. All we do is report the error to the user using an alert,
and release the connection so that we’re not leaking memory. We also stop the activity
indicator so that the user doesn’t think we’re still trying to retrieve the data.
- (void)connection:(NSURLConnection *)connection
didFailWithError:(NSError *)error {
[connection release];
self.receivedData = nil;

UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Error"
message:[NSString stringWithFormat:
@"Connection failed! Error - %@ (URL: %@)",
[error localizedDescription],[[error userInfo]
objectForKey:NSErrorFailingURLStringKey]]
delegate:self
cancelButtonTitle:@"Bummer"
otherButtonTitles:nil];
[alert show];
[alert release];
[spinner stopAnimating];
}
Finally, when all the data has been retrieved, our delegate method
connectionDidFinishLoading: gets called. We check the request type that we set
earlier, and use the received data to populate either the text view or the image view. We
also stop the activity indiator, and release the connection so that we don’t leak memory.
- (void)connectionDidFinishLoading:(NSURLConnection *)connection {

if (requestType == kRequestTypeImage) {
imageView.hidden = NO;
textView.hidden = YES;
imageView.image = [UIImage imageWithData:receivedData];
}
else {
CHAPTER 10: Working with Data from the Web
350
imageView.hidden = YES;
textView.hidden = NO;
NSString *payloadAsString = [[NSString alloc] initWithData:receivedData
encoding:NSUTF8StringEncoding];
textView.text = payloadAsString;
[payloadAsString release];
}

[connection release];
self.receivedData = nil;
[spinner stopAnimating];
[self performSelector:@selector(clear) withObject:nil afterDelay:5.0];
}
Well, that’s better. Take it out for a spin. Try changing the two URLs to point to bigger
files if you want to really see the difference that asynchronous retrieval can make in your
application.
At this point, you should have a pretty good handle on retrieving static data. But there’s
more to the Web than getting files from static URLs so, before we leave the chapter,
let’s take a quick look at how to change the request type and pass form parameters so
that you can also retrieve information from web applications and web services.
Request Types and Form Parameters
The Web is so much more than a network of static files now. The Internet is chock full of

various forms of web applications. If you need to pull data from a web service or other
form of web application, then a standard GET request like the ones we’ve been creating
aren’t going to cut it for you. Fortunately, the iPhone’s URL handling system is capable
of creating any type of HTTP request that you might need.
Specifying the HTTP Request Types
The HTTP protocol actually defines multipe types of requests. In addition to the
standard GET request that we’ve been using, there’s also something called a POST
request, which is used by most web forms. There’s also the lesser-used PUT, which is
used to add or replace an existing resource with a new one, and DELETE which is used
to remove a resource or make it unavailable.
In the early days of the Web, GET was used to retrieve static files and POST was used
for pretty much any kind of interactivity. As a result, there are a lot of web applications
and services that still use only GET and POST. With the rising popularity of RESTful web
services, many newer web applications do require requests to use the proper request
type depending on the task they are seeking to perform. We’re not going to try and
teach you the nuances of when to use each of the different HTTP request types. Our
goal is to show you how to specify the type of your request and pass the necessary
parameters so that you can retrieve data from web applications regardless of which
request type you need to use.

×