Introduction

This article follows previous articles on components for a RESTful iPhone application and an example RESTful service using Jersey (https://www.captechconsulting.com/blogs/implementing-iphone-friendly-rest-service-jersey-spring-and-jaxb). This article walks through the iPhone application to display the data provided by the REST service.

Components

The iPhone application for this tutorial only uses components provided in the iPhone SDK from Apple. The application will use the NSURLConnection class and NSXMLParser class for most of the heavy lifting required to consume a RESTful service with XML data.

All of the code for the tutorial is attached to this blog article (see the end of the article). The important classes in the tutorial code are:

  • BaseRestClient - This class provides methods that will be needed by any REST client code including the code to start parsing an XML response from a REST service.
  • GetUserListRestClient - An extension of BaseRestClient that contains the code to call and parse the response from the REST service that provides the list of users. This class uses the asynchronous mode of the NSURLConnection class.
  • GetUserRestClient - An extension of BaseRestClient that contains the code to call and parse the response from the REST service that provides details on a specific user. This class uses the synchronous mode of the NSURLConnection class.
  • RootViewController - The UITableViewController for the first table view seen in the application. This calls the GetUserListRestClient class to fetch the list of users.
  • UserDetailTableViewController - The table view controller for the table that show the details of a user object.
  • User - A model object that contains the data for a user object as returned by the REST service.

Component Interaction

The flow of the attached example program is as follows:

  • iPhone loads the binary and instantiates the RootViewController object indirectly through the MainWindow.xib file. This bit of interaction is all driven by the standard SDK as part of the application startup lifecycle.
  • Once loaded the RootViewController creates a GetUserListRestClient object and calls a business method on that object. That method returns upon initiating a network request, but before completion of that request.
  • Once the network request completes the connection delegate object calls the RootViewController with the user list just retrieved from the server.
  • The table in the RootViewController reloads, displaying the retrieved values.

When the user selects a value the table the following sequence of events occur:

  • The RootViewController creates a UserDetailTableViewController and passes it the userId of the selected user.
  • The RootViewController pushes the new controller onto the navigation controller stack of view controllers.
  • Once the UserDetailTableViewController loads it creates a GetUserRestClient object and calls it's primary business method with the id of the user to retrieve.
  • The GetUserRestClient returns the retrieved and fully populated user object.
  • The view controller populates the table rows with values from the user object.

Asynchronous Requests

The first view seen by a user of the application is controlled by the RootViewController class. This class utilizes the GetUserListRestClient class to fetch an array of partially populated User objects asynchronously. In other words, the call is issued to the network service, but control returns to the calling method before the network call completes.

The method below, from the RootViewController class, is called immediately after the view loads and when the user presses the refresh button.

- (void) fetchData {
 GetUserListRestClient *client = [[GetUserListRestClient alloc] init];
 [client getUsers:self];
 [client release];
}

This method creates an instance of the client object, calls the method to get the User list and then releases the object.

Because this REST client is asynchronous no data is returned to the view controller at this point. The getUsers method takes an argument specifying the object to call when data is returned; in this example the object is the view controller.

Releasing the client class just after allocation appears to be a dangerous action but isn't in this case because code within the getUsers method increments the retain count of the client object; therefore the client object will not be freed by the memory manager.

Performing an asynchronous request from the main thread in the iPhone app allows other events to continue to be processed on the main thread, such as view updates and other asynchronous NSURLConnection requests. The asynchronous mode of NSURLConnection is intended for use on the main thread of the application, using in secondary threads is problematic and requires some additional complicated programming.

In the example program, the GetUserListRestClient contains the code to perform the asynchronous connection and to parse the response.

The following snippets are from the getUsers method with extensive comments interleaved. The start of the method allocates and assigns variables that will be needed during the duration of the asynchronous request. In this case, the object to call back when complete is retained in the object attribute named controller.

- (void) getUsers:(id)ctl {
 // save the controller to call back
 self.controller = ctl;
 ...

The example application uses the NSUserDefaults object to store the base URL for the REST service. The file Settings.bundle communicates the settable parameters to the iPhone's settings subsystem. Once you install the application on a phone or in the simulator, the Settings application will contain a new settings session called 'Tutorial1'. This section has one setting, the URL of the server. The default setting assumes that the app is running in the simulator and that the REST service on the same host.

The result of the activity is an NSURL object pointing to the REST endpoint. Both the urlString and url objects are set for auto-release by the memory manager. The defaults object is a reference to a shared object that should not be retained or released by your application.

 ...
 // Grab the default base URL from the preferences
 <a href="https://developer.apple.com/reference/foundation/userdefaults">NSUserDefaults</a> *defaults = [<a href="https://developer.apple.com/reference/foundation/userdefaults">NSUserDefaults</a> standardUserDefaults];
 <a href="https://developer.apple.com/reference/foundation/nsstring">NSString</a> *urlString = [<a href="http://developer.apple.com/documentation/Cocoa/Reference/Foundation/Classes/NSString_Class/">NSString</a> stringWithFormat:@"%@/v2/list", [defaults stringForKey:@"URL_preference"]];
 NSLog(@"Sending Request to URL %@", urlString);
 
 <a href="https://developer.apple.com/reference/foundation/nsurl">NSURL</a> *url = [<a href="https://developer.apple.com/reference/foundation/nsurl">NSURL</a> URLWithString:urlString];
 ...

Once the URL is created the app starts the work of communicating. The next line turns on the network activity indicator on the phone status bar.

 ...
 [UIApplication sharedApplication].networkActivityIndicatorVisible = YES;
 ...

The NSURLRequest object contains all of the components of an HTTP request. In this example, the code is creating a request object, set for auto-release, which bypasses the browser cache and has an idle timeout of 30 seconds. If your request uses an HTTP method other than GET, the request object will need to be an NSMutableURLRequest so that you can change the request method and possibly add a request body.

 ...
 <a href="https://developer.apple.com/reference/foundation/nsurlrequest">NSURLRequest</a> *req = [<a href="https://developer.apple.com/reference/foundation/nsurlrequest">NSURLRequest</a> requestWithURL:url cachePolicy:NSURLRequestReloadIgnoringCacheData timeoutInterval:30.0];
 ...

Finally, the request initiates the connection using the just created request object. The connection retains the current object as the delegate. This prevents the current object from being freed by the memory manager (remember that it was immediately released in the RootViewController). After starting the request, the method returns control to the RootViewController.

 ...
 // start the async request
 [<a href="https://developer.apple.com/reference/foundation/nsurlconnection">NSURLConnection</a> connectionWithRequest:req delegate:self];
 return;
}

As the asynchronous request processes, the OS will call the delegate object to report the progress of the request. If no authentication requests are to be handled then there are only four delegate methods to implement.

The first of these methods is the connection:didReceiveResponse: method. This method is called when the HTTP response header is received from the server. This method may get called multiple times in certain odd circumstances. Therefore, Apple recommends that you dispose of all received data if the method is called subsequent times on the same request.

The method in the example starts with a simple log statement.

- (void)connection:(<a href="https://developer.apple.com/reference/foundation/nsurlconnection">NSURLConnection</a> *)connection didReceiveResponse:(<a href="https://developer.apple.com/reference/foundation/urlresponse">NSURLResponse</a> *)response {
 NSLog(@"Received Response");
 ...

This portion of the method validates that the HTTP status returned indicates a successful return from the server. The example does rudimentary error handling, a real app should respond more appropriately.

 ...
 if ([response isKindOfClass:[<a href="https://developer.apple.com/reference/foundation/httpurlresponse">NSHTTPURLResponse</a> class]]) {
 <a href="https://developer.apple.com/reference/foundation/httpurlresponse">NSHTTPURLResponse</a> *httpResponse = (<a href="https://developer.apple.com/reference/foundation/httpurlresponse">NSHTTPURLResponse</a> *) response;
 int status = [httpResponse statusCode];
 
 if (!((status >= 200) && (status <>300))) {
 NSLog(@"Connection failed with status %@", status);
 [UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
 } else {
 ...

This section of the code handles the success case. It allocates a buffer into which data received from the service will be stored. If you application expected large amounts of data on a request, this would be the place to create a temporary file to buffer than information during transmission and before parsing. The NSMutableData object will expand as data is added, so 1024 bytes is not a hard size limit for this buffer.

 ...
 // make the working space for the REST data buffer. This could also be a file if you want to reduce the RAM footprint
 [wipData release];
 wipData = [[<a href="https://developer.apple.com/reference/foundation/nsmutabledata">NSMutableData</a> alloc] initWithCapacity:1024];
 }
 }
}

The next method that is implemented for this simple example is the connection:didReceiveData: method. If large amounts of data are returned from the server, this method may be called multiple times. In the example code the method appends the received data to the buffer created when the initial response was received.

- (void)connection:(<a href="https://developer.apple.com/reference/foundation/nsurlconnection">NSURLConnection</a> *)connection didReceiveData:(<a href="https://developer.apple.com/reference/foundation/nsdata">NSData</a> *)data {
 [wipData appendData:data];
}

The third method that may be called is the failure method. This method may be called any time during the request, including before the didReceiveResponse method. Again, only rudimentary error handling is implemented in the example code.

- (void)connection:(<a href="http://developer.apple.com/documentation/Cocoa/Reference/Foundation/Classes/NSURLConnection_Class/">NSURLConnection</a> *)connection didFailWithError:(<a href="https://developer.apple.com/reference/foundation/nserror">NSError</a> *)error {
 NSLog(@"Connection failed");
 
 [UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
}

The forth, and final, method necessary for asynchronous HTTP request is the connectionDidFinishLoading method. This delegate method is called by the NSURLConnection object when all the data has been received and the didReceiveData method is complete. The following code snippets comprise the finish loading method of the example.

The first action of the method is to dump out the received XML. Normally, this would be surrounded in an #ifdef DEBUG wrapper.

- (void)connectionDidFinishLoading:(<a href="https://developer.apple.com/reference/foundation/nsurlconnection">NSURLConnection</a> *)conn {
 // do a little debug dump
 <a href="https://developer.apple.com/reference/foundation/nsstring">NSString</a> *xml = [[<a href="https://developer.apple.com/reference/foundation/nsstring">NSString</a> alloc] initWithData:wipData
 encoding:NSUTF8StringEncoding];
 NSLog(@"xml = %@", xml);
 [xml release];
 ...

The first real work of the method is to create an NSXMLParser object and to initiate parsing of the received data. The parseDocument method is declared in the superclass of this object. It creates the NSXMLParser, sets some default parser parameters, initiates the parsing with the current object as the delegate object, and releases the parser when complete.

 ...
 // create and start parser
 [self parseDocument:responseData];
 ...

After completing the parsing, the method will pass the received results array to the controller object. Recall that the controller object value was set at the beginning of the getUsers method. The controller object references the RootViewController object that initiated the REST request. Before calling the controller, the code verifies that the controller can handle a method call to a method named updateTable:. The final line of the method turns off the network activity indicator.

 ...
 // after parsing, call the controller to report that the tranfer is done
 if ([controller respondsToSelector:@selector(updateTable:)]) {
 [controller performSelector:@selector(updateTable:) withObject:results];
 }
 // turn off the network indicator
 [UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
}

The updateTable method in the RootViewController simply retains the data array passed from the GetUserListRestClient and then instructs the tableView to reload itself.

- (void) updateTable:(<a href="https://developer.apple.com/reference/foundation/nsarray">NSArray</a> *)data {
 self.userList = data;
 
 [self.tableView reloadData];
}

Synchronous Requests

The synchronous request method is much simpler, but has the potential risk that the application will pause while the data is retrieved from the server. If the connection to the server is poor, this will result in an unsatisfactory experience for the user. Additionally, the synchronous request does not handle authentication challenges from the server. One advantage of the synchronous methods is that they can be easily used within sub-threads of your application and they are simplier to implement.

The code snippet below is from the getUser method in the GetUserRestClient object. The beginning of the method is essentially identical to the asynchronous method.

The asynchronous mode of NSURLConnection requires that the caller pass in the NSURLRequest object, a pointer to a pointer to NSURLResponse and NSErrorobjects. Control is not returned from the sendSynchronousRequest method call until the request completes or fails.

<a href="https://developer.apple.com/reference/foundation/nserror">NSError</a> *requestError;
<a href="https://developer.apple.com/reference/foundation/urlresponse">NSURLResponse</a> *urlResponse;
<a href="https://developer.apple.com/reference/foundation/nserror">NSError</a> *error = nil;
<a href="https://developer.apple.com/reference/foundation/nsdata">NSData</a> *responseData = [<a href="https://developer.apple.com/reference/foundation/nsurlconnection">NSURLConnection</a> sendSynchronousRequest:req returningResponse:&urlResponse error:&requestError];

After the call returns, if the pointer to the NSError points to nil then the call succeeded and will have a response code from the server. If the NSError pointer points to an actual NSError object then the call failed before leaving the device.

In the example code, the response object is cast to an NSHTTPURLResponse object and the return status is validated. If the return is a good HTTP return code then the returned data is used to initialize an NSXMLParser object, as in the asynchronous method.

if (error == nil) {
 if ([urlResponse isKindOfClass:[<a href="https://developer.apple.com/reference/foundation/httpurlresponse">NSHTTPURLResponse</a> class]]) {
 <a href="https://developer.apple.com/reference/foundation/httpurlresponse">NSHTTPURLResponse</a> *httpResponse = (<a href="http://developer.apple.com/documentation/Cocoa/Reference/Foundation/Classes/NSHTTPURLResponse_Class/">NSHTTPURLResponse</a> *) urlResponse;
 int status = [httpResponse statusCode];
 // if the call was okay, then invoke the parser
 if ((status >= 200) && (status <>300)) {
 [self parseDocument:responseData];
 }
 }
}

With the synchronous calls, the flow of control returns to the calling UITableViewController only after the call has completed or failed. Therefore, there is no need for the controller to pass a reference to itself or to be able to handle a callback via a delegate method. In the example code, the UserDetailTableViewController creates and calls the REST client object from within the ViewDidLoad method without the need for a callback method.

- (void)viewDidLoad {
 [super viewDidLoad];
 
 GetUserRestClient *client = [[GetUserRestClient alloc] init];
 self.user = [client getUser:self.userId];
 [client release];
}

Parsing XML

The parsing of the received XML is initiated in the BaseRestClient class's

parseDocument: method, shown below. The first portion creates and initializes the parser using the data object received from the HTTP request.

- (void) parseDocument:(NSData *) data { NSXMLParser *parser = [[NSXMLParser alloc] initWithData:data]; ...

This next segment sets the parser to be the current object. Since the REST client classes are sub-classes of the BaseRestClient the calls to the delegate methods will be made up on the class at the lowest point in the hierarchy that has the method declared. Immediately following the code specifies the handling of namespaces and external references.

 ...
 [parser setDelegate:self];
 // Depending on the XML document you're parsing, you may want to enable these features of NSXMLParser.
 [parser setShouldProcessNamespaces:YES];
 [parser setShouldReportNamespacePrefixes:YES];
 [parser setShouldResolveExternalEntities:NO];
 ...

Finally, start the parsing process. The NSXMLParser will call the delegate class as data is encountered within the XML document or as errors occur.

 ...
 [parser parse];
 [parser release];
}

The NSXMLParser support almost two dozen delegate methods. For a minimalistic client, like in the example, only three methods are used.

The parser:foundCharacters: method is declared in the BaseRestClient class. It is used to accumulate the text between element tags. This method may be called repeatedly as more text is encountered. The code in the example accumulates the parsed text into a NSMutableString buffering object. At the end of every element, that buffer is cleared. If your REST client could receive CDATA values then you should implement the

parser:foundCDATA: method as well.

The next method implemented in the example is the didStartElement

method. This method is called by the NSXMLParser at the beginning of each element. The example code merely clears the accumulated text contents. If the code were dealing with complex XML with nested or repeating elements this method would need to manage and track the state of the document as it is parsed. Any required handling of element attributes would occur in the didStartElement method also.

- (void)parser:(<a href="https://developer.apple.com/reference/foundation/xmlparser">NSXMLParser</a> *)parser didStartElement:(<a href="http://developer.apple.com/documentation/Cocoa/Reference/Foundation/Classes/NSString_Class/">NSString</a> *)elementName namespaceURI:(<a href="http://developer.apple.com/documentation/Cocoa/Reference/Foundation/Classes/NSString_Class/">NSString</a> *)namespaceURI qualifiedName:(<a href="http://developer.apple.com/documentation/Cocoa/Reference/Foundation/Classes/NSString_Class/">NSString</a> *)qName attributes:(<a href="http://developer.apple.com/documentation/Cocoa/Reference/Foundation/Classes/NSDictionary_Class/">NSDictionary</a> *)attributeDict
{
 [self clearContentsOfElement];
}

The final method implemented in the example code related to XML parsing is the didEndElement method. This method is called by the parser at the end of each XML element. At this point, the intra-element text buffer should be fully populated with any text values.

The example code compares the elementName of the just finished element to determine a code path to execute. As elements arrive, object attributes are populated in the work in progress User object (wipUser). Once the parsing is complete the wipUser is returned to the calling view controller.

- (void)parser:(<a href="https://developer.apple.com/reference/foundation/xmlparser">NSXMLParser</a> *)parser didEndElement:(<a href="http://developer.apple.com/documentation/Cocoa/Reference/Foundation/Classes/NSString_Class/">NSString</a> *)elementName namespaceURI:(<a href="http://developer.apple.com/documentation/Cocoa/Reference/Foundation/Classes/NSString_Class/">NSString</a> *)namespaceURI qualifiedName:(<a href="http://developer.apple.com/documentation/Cocoa/Reference/Foundation/Classes/NSString_Class/">NSString</a> *)qName
{
 if ([elementName isEqualToString:@"UserId"]) {
 [wipUser setUserId:[<a href="https://developer.apple.com/reference/foundation/nsnumber">NSNumber</a> numberWithInt:[_contentsOfElement intValue]]];
 } else if ([elementName isEqualToString:@"UserName"]) {
 [wipUser setUserName:_contentsOfElement];
 } else if ([elementName isEqualToString:@"ShoeSize"]) {
 [wipUser setShoeSize:_contentsOfElement];
 }
 [self clearContentsOfElement];
}

Example Code

The full example code for this tutorial is attached to this blog article as Tutorial.zip. The zip file should be downloaded, and expanded into a working directory. Open the file Tutorial1.xcodeproj using XCode 3.1 or higher to access the project code.