O'Reilly logo

Developing Enterprise iOS Applications by James Turner

Stay ahead with the world's most comprehensive technology and business learning platform.

With Safari, you learn the way you learn best. Get unlimited access to videos, live online training, learning paths, books, tutorials, and more.

Start Free Trial

No credit card required

Chapter 4. Integrating iOS Applications into Enterprise Services

In the early days of software development, you ran your programs directly on a computer, sitting in front of the console. Later, the idea of timesharing and remote sessions came into being, bringing with it the rise of the 3270 and VT-100 terminals, along with punch cards and paper tape. Later, network computing became the rage, with Sun famously proclaiming that the network was the computer. We got RPC, CORBA, and we’ve evolved today into SOAP, REST, and AJAX. But no matter what it’s called, or what format the data moves in, all these technologies attempt to solve the same problem, the same one that has existed since client-server architectures first came upon the earth.

As an iOS developer, you face the same problem today. You need to be able to reliably and (hopefully) easily integrate user-facing UI with all the messy business logic, persistence, security, and magic unicorn blood that does all the hard work on the backend.[1]

The Rules of the Road

There are basically two possible scenarios when integrating iOS applications into Enterprise services. Either you are starting from scratch on both ends, or you have some legacy service with which you need to ingratiate your new application. The former situation is vastly preferable, because you can fine-tune your protocols and payloads to the demands of mobile clients, but the later scenario is probably the more likely one. In any event, I’ll talk in general about each of them, and then look at some specific techniques for dealing with various protocols and payloads under iOS.

Rule 1: Insist on Contract-Driven Development

Modern IDEs and service toolkits can take a lot of the grunt work out of creating service interfaces. They can auto-generate classes for you, create testing frameworks, generate documentation, and I wouldn’t be surprised if they could prepare world-class sushi on demand by now. But there’s one thing that they can do that is the bane of every client developer on the planet, and that’s to auto-generate the interface from the backing classes. Java-based SOAP development is getting to be semi-notorious for this. On paper, it’s great. Just sprinkle magic annotations in your source code, and tools such as CXF will figure it all out, generate the WSDL files, and you’re good to go.

Unfortunately, in reality, you end up with a couple of problems. For one, you give up control over what the SOAP structures (or RESTful XML structures) are going to look like. I have seen some unforgivably butt-ugly XML produced by class-driven interface development. But more seriously, it means that “innocent” changes made to the underlying Java classes can have unforeseen repercussions on the payload, causing mysterious failures. There’s nothing like doing your first regression test against a new server release where “nothing changed,” and finding out that the payloads have been fundamentally altered by a trivial change in the server-side classes.

Because of this, I consider it almost a necessity that both sides of the house adhere to a contract-first approach to APIs. TDD is great for this: you can write some SOAPUI tests to exercise the specified contract, which will fail until the service is implemented. This keeps the server-side developers honest, because the acceptance test for their service is whether or not it passes the SOAPUI tests, and later on down the road, it will make sure they don’t break the API contract.

Rule 2: Be Neither Chunky Nor Chatty

A friend of mine, web services guru Anne Thomas Manes of Burton Group, likes to say that web services should be chunky, not chatty. That is to say, it’s better to send down too much information in a single request, rather than make dozens of calls to get the data, paying the latency toll on each request.

In general, I agree with her, especially since in a 3G (or worse) mobile environment, the latency can get quite painful. But on mobile devices, keeping down the payload size can be just as important. Not only do many users pay by the byte, but you have much more restrictive memory profiles on mobile devices. Thankfully, this isn’t as much of a factor on an iOS device, because there tends to be plenty of memory available to the foreground process, but on Android or J2ME devices, available memory can be quite limited. If you suck down a 4MB XML payload as a string, and then parse it into an XML DOM, you could have 8MB of memory being consumed just by the data.

Of course, if you’re only developing the protocol for iOS consumption, you only need to worry about bankrupting your users when they get their data plan bill, but these days, no one develops backend services for a single mobile platform, so you may have to live with constraints imposed by the needs of lesser devices. In a perfect world, try to design your protocols to allow for both chunky and chatty requests, and let the client decide how much data they want to consume at once.

First Things First: Getting a Connection

I’m going to make a general assumption for the body of this chapter, which is that whatever you are talking to, you’re doing it via HTTP (or HTTPS). If you’re doing something custom using sockets, the 1990s are calling and they would like their architecture back. In all seriousness, though, almost all client-server mobile work these days is done using HTTP for transport.[2]

The iOS SDK comes with a perfectly adequate HTTP implementation, found in the CFNetwork framework. But anyone who uses it is a masochist, because it makes you do so much of the work yourself. Until recently, I would have recommended using the ASIHttpRequest package. ASI stands for All Seeing Interactive (or “eye”), by the way, so if you happen to be an Illuminati conspiracist, this may not be the software for you. Why did I like this package so much?

It is licensed under the über-flexible BSD license, so you can do pretty much anything you want with it, and it provides a lot of the messy housekeeping code that HTTP requests tend to involve. Specifically, it will automatically handle:

Easy access to request and response headers
Authentication support
Cookie support
GZIP payload support
Background request handling in iOS 4 and later
Persistent connections
Synchronous and asynchronous requests
(And more!)

However, after the early access version of this book was released, I learned that ASIHttpRequest is in danger of becoming an orphan project. This is especially disturbing, because it means it may never be converted over to ARC, among other things. It would be more disturbing if it were the only game in town; until recently, it was, because the system-provided alternative, NSURLConnection, didn’t support asynchronous requests. Being able to choose synchronous and asynchronous requests is critically important. Almost all of the time, you want to use asynchronous requests, even if the request seems synchronous. For example, a login may seem synchronous. After all, you can’t really do anything further until the request completes. But in reality, if you do a real synchronous HTTP request, everything locks up (including activity indicators) until the response returns. The pattern you want to use is to lock the UI (typically by setting various UI elements to have userInteractionEnabled set false), and then make an asynchronous call with both the success and failure callbacks re-enabling the UI.

Thankfully, in iOS 5, the system-provided class was extended to include asynchronous requests, so there’s no longer a reason to avoid using it.

Using NSURLConnection—The BuggyWhip News Network

Let’s see this package put through its paces, in this case by creating a page in the BuggyWhipChat application that lets you see the latest news on buggy whips. Unfortunately, the server folks haven’t gotten the news feed working yet, so we’ll be using CNN for testing. The page itself is going to be simple, a new button on the iPad toolbar that gets the CNN home page, and displays the HTML in an alert. Yes, this is a totally useless example, but we are talking about BuggyWhipCo here! Actually, the reason that we’re not doing more with the HTML is two-fold: there’s a perfectly good UIWebView control that already does this, and the point is to highlight the NSURLConnection package, not to display web pages.

First, we add the toolbar item to the detail XIB and hook it up the “Sent Action” for the button to a new IBAction called showNews in the view controller, as shown in Example 4-1.

Example 4-1. A simple example using NSURLConnection
- (void)handleError:(NSString *)error{
    UIAlertView *alert = 
            [[UIAlertView alloc]
             initWithTitle:@"Buggy Whip News Error" 
             message: error
             delegate:self cancelButtonTitle:@"OK"
             otherButtonTitles:nil, nil];
    [alert show];
}

-(IBAction) showNews:(id) sender {
    NSURL *url = [NSURL URLWithString:@"http://www.cnn.com/index.html"];
    NSURLRequest *request = [NSURLRequest requestWithURL:url];
    [NSURLConnection
      sendAsynchronousRequest:request
      queue:[NSOperationQueue currentQueue]
      completionHandler:^(NSURLResponse *response,
                          NSData *data, NSError *error) {
         if (error != nil) {
             [self handleError:[error localizedDescription]];
         } else {
             [self displayResponse:data];
             if ([response isKindOfClass:[NSHTTPURLResponse class]]) {
                 NSHTTPURLResponse *httpresp =
                  (NSHTTPURLResponse *) response;
                 if ([httpresp statusCode] == 404) {
                     [self handleError:@"Page not found!"];
                 } else {
                     [self displayResponse:data];
                 }
             }
         }
     }];
}

So, what’s going on here? Most of the action takes place in the showNews method, which is triggered by the button push of the new tab bar item. When it is pressed, the code creates an NSURL reference to the CNN website, and then creates an NSURLRequestHttpRequest instance using the requestWithURL method. There are a number of parameters you can set on the returned object, some of which I cover later, but by default, you get an HTTP GET request. Next, we use the sendAsynchronousRequest method of the NSURLConnection class to actually perform the request. This method is handed three parameters: the request we just created, the NSOperationQueue to run the request on (which, for thread safety, will usually be the current queue), and a block which serves as the completion handler (that is, the handler that deals with successful or failed calls).

If the request fails catastrophically (connection failures, server failures, etc.), the failure is returned in the error parameter, which will be non-null. If the call succeeds, which can include 401 (unauthorized) and 404 (not found) errors, the call response is populated along with the body of the response as an NSData object. You can proceed to process the response body, returned in the data parameter.

If we run the application and click on the tab bar button, we get the expected results, shown in Figure 4-1.

If we change the hardwired string that we use to create the URL to something bogus, such as http://www.cnnbuggywhip.com, we get an alert message with the error shown in Figure 4-2.

But suppose we specify a valid host name, but an invalid address, such as http://www.cnn.com/buggywhipco.html? As I mentioned, a 404 error is considered a success and will call the success selector, leading to something like the alert in Figure 4-3.

What we really want to have happen here is to display a meaningful alert message, something that we could internationalize, for example. So, we need to look a bit more carefully at allegedly “good” responses, leading to the rewritten method shown in Example 4-2.

Fetching the CNN home page
Figure 4-1. Fetching the CNN home page
The response to a bogus URL
Figure 4-2. The response to a bogus URL
Connecting to a bad address on a good server
Figure 4-3. Connecting to a bad address on a good server
Example 4-2. A more robust success method
-(IBAction) showNews:(id) sender {
    NSURL *url = [NSURL URLWithString:@"http://www.cnn.com/index.html"];
    NSURLRequest *request = [NSURLRequest requestWithURL:url];
    [NSURLConnection
      sendAsynchronousRequest:request
      queue:[NSOperationQueue currentQueue]
      completionHandler:^(NSURLResponse *response,
                          NSData *data, NSError *error) {
         if (error != nil) {
             [self handleError:[error localizedDescription]];
         } else {
             if ([response isKindOfClass:[NSHTTPURLResponse class]]) {
                 NSHTTPURLResponse *httpresp =
                  (NSHTTPURLResponse *) response;
                 if ([httpresp statusCode] == 404) {
                     [self handleError:@"Page not found!"];
                 } else {
                     [self displayResponse:data];
                 }
             }
         }
     }];
}

What has happened here is that we have checked to see that the response is really an NSHTTPURLResponse object (which it should be for any HTTP request). If so, you can cast the response object to that type, and use the statusCode method to check that you haven’t gotten a 404, 401, or other HTTP protocol error.

Something a Little More Practical—Parsing XML Response

Now that we’ve had a brief introduction to using NSURLRequest, we can use it to do something a bit more useful than spitting out raw HTML in alerts. We still don’t have any web services from the backend developers, so we’ll use a publicly available RESTful web service provided by the US National Weather Service. Given an address such as: http://www.weather.gov/forecasts/xml/sample_products/browser_interface/ndfdXMLclient.php?zipCodeList=20910+25414&product=time-series&begin=2004-01-01T00:00:00&end=2013-04-21T00:00:00&maxt=maxt&mint=mint

We can get back XML that looks like the sample shown in Example 4-3.

Example 4-3. Sample XML from the National Weather Service
<dwml xmlns:xsd="http://www.w3.org/2001/XMLSchema"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      version="1.0"
      xsi:noNamespaceSchemaLocation=\       
      "http://www.nws.noaa.gov/forecasts/xml/DWMLgen/\
      schemaDWML.xsd">
   <head>
      <product srsName="WGS 1984"
               concise-name="time-series"
               operational-mode="official">
         <title>
           NOAA's National Weather Service Forecast Data
         </title>
         <field>meteorological</field>
         <category>forecast</category>
         <creation-date refresh-frequency="PT1H">
                2011-07-25T00:30:56Z
         </creation-date>
      </product>
      <source>
         <more-information>
           http://www.nws.noaa.gov/forecasts/xml/
         </more-information>
         <production-center>
           Meteorological Development Laboratory
           <sub-center>
              Product Generation Branch
           </sub-center>
         </production-center>
         <disclaimer>
             http://www.nws.noaa.gov/disclaimer.html
         </disclaimer>
         <credit>
             http://www.weather.gov/
         </credit>
         <credit-logo>
              http://www.weather.gov/images/xml_logo.gif
         </credit-logo>
         <feedback>
             http://www.weather.gov/feedback.php
         </feedback>
      </source>
   </head>
   <data>
      <location>
         <location-key>point1</location-key>
         <point latitude="42.90" longitude="-71.29"/>
      </location>
      <moreWeatherInformation applicable-location="point1">
          http://forecast.weather.gov/MapClick.php?\
          textField1=42.90&textField2=-71.29
      </moreWeatherInformation>
      <time-layout time-coordinate="local"
          summarization="none">
          <layout-key>k-p24h-n1-1</layout-key>
          <start-valid-time>
              2011-07-26T08:00:00-04:00
          </start-valid-time>
          <end-valid-time>
             2011-07-26T20:00:00-04:00
          </end-valid-time>
      </time-layout>
      <time-layout time-coordinate="local"
         summarization="none">
         <layout-key>k-p24h-n1-2</layout-key>
         <start-valid-time>
             2011-07-26T20:00:00-04:00
         </start-valid-time>
         <end-valid-time>
             2011-07-27T09:00:00-04:00
         </end-valid-time>
      </time-layout>
      <parameters applicable-location="point1">
         <temperature type="maximum" 
            units="Fahrenheit" time-layout="k-p24h-n1-1">
            <name>Daily Maximum Temperature</name>
            <value>82</value>
         </temperature>
         <temperature type="minimum" 
             units="Fahrenheit" time-layout="k-p24h-n1-2">
             <name>Daily Minimum Temperature</name>
             <value>61</value>
         </temperature>
      </parameters>
   </data>
</dwml>

There’s a mouthful of data in that payload, but essentially, given a zip code and a start and end date, the service will return a list of high and low temperature forecasts that lie between those dates. Let’s use this service to create a more interesting alert.

We can start by writing a utility function to generate the correct endpoint for the REST request, given a series of parameters. By creating a stand-alone generator, it will be easier to unit test that logic. So, we create a WebServiceEndpointGenerator class, with a static method to create the appropriate endpoint, which looks like Example 4-4.

Example 4-4. A web service endpoint generator
+(NSURL *) getForecastWebServiceEndpoint:(NSString *) zipcode startDate:(NSDate *)
      startDate endDate:(NSDate *) endDate {
    NSDateFormatter *df = [NSDateFormatter new];
    [df setDateFormat:@"YYYY-MM-dd'T'hh:mm:ss"];
    NSString *url =
         [NSString stringWithFormat:
            @"http://www.weather.gov/forecasts/xml/\
              sample_products/browser_interface/\
              ndfdXMLclient.php?zipCodeList=%@\
              &product=time-series&begin=%@\
              &end=%@&maxt=maxt&mint=mint",
         zipcode, [df stringFromDate:startDate], 
         [df stringFromDate:endDate]];
    [df release];
    return [NSURL URLWithString:url];
}

Back on our detail page, we add another bar button hooked up to a new selector, called showWeather. To save some space here, and because we’re interested in the backend code, not the UI treatment, we’ll have the success method for this call simply send the output to the log using NSLog. But how to extract the useful data? We could use string functions to find the pieces of the XML we were interested in, but that’s laborious and not very robust. We could use the XML pull parser that comes with iOS, but pull parsers are also a bit of a pain, and I tend to avoid using them unless I need to significantly parse down the in-memory footprint of the data. So, since iOS doesn’t have a native DOM parser, we’ll dig one out of the open source treasure chest.

You actually have your choice of packages, TouchXML and KissXML. I prefer KissXML, because it can both parse XML to a DOM, and generate XML from a DOM, whereas TouchXML can only parse. KissXML is available at: http://code.google.com/p/kissxml. Integration largely involves copying files into the project, adding the libxml2 library, and one additional step: adding a directory to the include search path. The KissXML Wiki has all the details on how to add the package to your project.

I’ll note here that KissXML is, at the moment, not compatible with the ARC compiler. For that reason, I highly recommend making it into a static library that you include in your main application, as described in Chapter 2.

With the library in place, we can use XPath to pluck just the data we want out of the returned payload from the web service; the code is shown in Example 4-5.

Example 4-5. Parsing XML with KissXML
-(id) getSingleStringValue:(DDXMLElement *) element xpath:(NSString *) xpath {
     NSError *error = nil;
     NSArray *vals = [element nodesForXPath:xpath error:&error];
     if (error != nil) {
         return nil;
     }
     if ([vals count] != 1) {
         return nil;
     }
     DDXMLElement *val = [vals objectAtIndex:0];
     return [val stringValue];
 }


 - (void)gotWeather:(NSData *)data
 {
     UIAlertView *alert;
     NSError *error = nil;
     DDXMLDocument *ddDoc = [[DDXMLDocument alloc] 
                             initWithXMLString:[[NSString alloc] 
                                                initWithData:data 
                                                encoding:NSUTF8StringEncoding]
                             options:0 error:&error];
     if (error == nil) {
         NSArray *timelayouts =
         [ddDoc nodesForXPath:@"//data/time-layout" error:&error];
         NSMutableDictionary *timeDict = 
         [NSMutableDictionary new];
         for (DDXMLElement *timenode in timelayouts) {
             NSString *key = 
             [self getSingleStringValue:timenode 
                                  xpath:@"layout-key"];
             if (key != nil) {
                 NSArray *dates =
                 [timenode nodesForXPath:@"start-valid-time" error:&error];
                 NSMutableArray *dateArray = [NSMutableArray new];
                 for (DDXMLElement *date in dates) {
                     [dateArray addObject:[date stringValue]];
                 }
                 [timeDict setObject:dateArray forKey:key];
             }
         }
         NSArray *temps =
         [ddDoc nodesForXPath:@"//parameters/temperature" error:&error];
         for (DDXMLElement *tempnode in temps) {
             NSString *type = [self getSingleStringValue:tempnode 
                                                   xpath:@"@type"];
             NSString *units = [self getSingleStringValue:tempnode 
                                                    xpath:@"@units"];
             NSString *timeLayout = [self getSingleStringValue:tempnode 
                                                         xpath:@"@time-layout"];
             NSString *name = [self getSingleStringValue:tempnode xpath:@"name"];
             NSArray *values = [tempnode nodesForXPath:@"value" error:&error];
             int i = 0;
             NSArray *times = [timeDict valueForKey:timeLayout];
             for (DDXMLElement *value in values) {
                 NSString *val = [value stringValue];
                 NSLog(@"Type: %@, Units: %@, Time: %@",
                       type, units, [times objectAtIndex:i]);
                 NSLog(@"Name: %@, Value: %@",
                       name, val);
                 NSLog(@" ");
                 i++;
             }
         }
         return;
     }
     alert = [[UIAlertView alloc] initWithTitle:@"Error parsing XML" 
                                        message: [error localizedDescription]
                                       delegate:self 
                              cancelButtonTitle:@"OK" otherButtonTitles:nil, nil];
     [alert show];
 }

- (IBAction)showWeather:(id)sender {
    NSURL *url =
     [WebServiceEndpointGenerator 
      getForecastWebServiceEndpoint:@"03038"
      startDate:[NSDate date]
      endDate:[[NSDate date]
               dateByAddingTimeInterval:3600*24*2]];
    NSURLRequest *request = [NSURLRequest requestWithURL:url];
    [NSURLConnection
      sendAsynchronousRequest:request
      queue:[NSOperationQueue currentQueue]
      completionHandler:^(NSURLResponse *response,
                          NSData *data, NSError *error) {
         if (error != nil) {
             [self handleError:error.description];
         } else {
             [self gotWeather:data];
         }
     }];
}

Let’s take a good look at what’s going on here. First off, we have the new handler for the weather button we added to the tab bar, called showWeather. What this method does is to construct an endpoint using the generator we just created. It passes in the zip code for Derry, NH (my home sweet home), and start and end dates that correspond to now and 24 hours from now. Then it does an asynchronous request to that URL using the same completion handler mechanism that we used in the previous example.

One advantage that the NSURLConnection method has over my previous choice, ASIHttpRequest, is that it is purely block-driven for the handlers. This can be significant when you have multiple asynchronous requests in flight, because a block-based handler lets you tie the handler directly to the request, rather than using selectors to specify the delegates to handler the responses.

The interesting bit, from an XML parsing perspective, is what happens inside the gotWeather method when the request succeeds. After doing the normal checks for bad status codes, it uses the initWithXMLString method of the DDXMLDocument class to parse the returned string into an XML DOM structure. The method takes a pointer to an NSError object as an argument, and that is what you should check after the call to see if the parse was successful.

Assuming the parse succeeded, the next step is to use the nodesForXPath method on the returned XML document, specifying //data/time-layout as the XPath to look for. This method returns an array of DDXMLElement objects, or nil if nothing matched the XPath specified. The weather XML we get back follows a pattern I find particularly obnoxious in XML DTDs: it makes you match up data from one part of the payload with data from another part. In this case, the time-layout elements have to be matched up with the attributes in the temperature elements, so we iterate over the time-layout elements, building a dictionary of the layout names to the dates they represent.

I find when working with KissXML that I frequently want to get the string value of a child node that I know only has a single instance. So I usually write a helper method such as the getSingleStringValue method used in this example. Using it, I can snatch the keys and start dates, and populate the dictionary with them. This method is shown in Example 4-6.

Example 4-6. The getSingleStringValue method
-(id) getSingleStringValue:(DDXMLElement *) element
                      xpath:(NSString *) xpath {
    NSError *error = nil;
    NSArray *vals = [element nodesForXPath:xpath error:&error];
    if (error != nil) {
        return nil;
    }
    if ([vals count] != 1) {
        return nil;
    }
    DDXMLElement *val = [vals objectAtIndex:0];
    return [val stringValue];
}

Basically, the method takes the root element for the extraction, and a relative or absolute XPath expressing as a string. It gets the array of matching elements, makes sure there wasn’t an error (such as an invalid XPath string), and if it gets back exactly one element in the array, returns it.

Once I have the dictionary, I iterate over the temperature data, grabbing attributes and element values as appropriate, then use the diction to find the time associated with the reading. Finally, I dump the data out to the log, resulting in this kind of output:

Type: maximum, Units: Fahrenheit
2011-11-16T07:00:00-05:00, Daily Maximum Temperature = 58

2011-11-17T07:00:00-05:00, Daily Maximum Temperature = 49

2011-11-18T07:00:00-05:00, Daily Maximum Temperature = 48

Type: minimum, Units: Fahrenheit
2011-11-16T19:00:00-05:00, Daily Minimum Temperature = 42

2011-11-17T19:00:00-05:00, Daily Minimum Temperature = 27

Generating XML for Submission to Services

Whereas you almost always want to use a parsed DOM to extract data from a service, how you construct XML to send back to a server is more flexible. The particular web service we used in the last example is GET-only, so we’ll use a mythical service that takes a POST for this one. Let’s say that the server crew has finally gotten their act together, and you can now post a news item by sending the following XML:

<newsitem postdate="2011-07-01" posttime="14:54">
   <subject>
         Buggy Whips Now Available in Brown
   </subject>
   <body>
         Buggy whips are the hottest thing around these
         days, and now they're available in a new
         designer color, caramel brown!
   </body>
</newsitem>

If you wanted to construct payloads that follow this format, and post them up to the server, you could use the code snippet in Example 4-7.

Example 4-7. Constructing XML by formatting strings
-(NSString *) constructNewsItemXMLByFormatWithSubject:(NSString *) 
    subject body:(NSString *) body 
{
    NSDate *now = [NSDate date];
    NSDateFormatter *df = [NSDateFormatter new];
    [df setDateFormat:@"YYYY-MM-dd"];
    NSDateFormatter *tf = [NSDateFormatter new];
    [tf setDateFormat:@"hh:mm"];
    NSString *xml = 
       [NSString stringWithFormat:@"<newsitem\
         postdate=\"%@\" posttime=\"%@\">\
         <subject>%@<subject><body>%@</body></newsitem>",
                      [df stringFromDate:now], 
                      [tf stringFromDate:now], 
                      subject, body];

    return xml;
}

- (IBAction)sendNews:(id)sender {
    NSLog(@"By Format: %@", 
          [self constructNewsItemXMLByFormatWithSubject:
             @"This is a test subject"
             body:@"Buggy whips are cool, aren't they?"]);
}

If we hook the sendNews IBAction up to a new bar button item and run the app, we can see that perfectly reasonable XML is produced:

By Format: <newsitem postdate="2011-07-26" posttime="10:42"> \
<subject>This is a test subject<subject>\
<body>Buggy whips are cool, aren't they?</body></newsitem>

Let’s compare this with the corresponding code, done using DOM construction techniques (Example 4-8).

Example 4-8. Constructing XML by building a DOM
#import "DDXMLElementAdditions.h"
.
.
.
-(NSString *) constructNewsItemXMLByDOMWithSubject:
    (NSString *) subject body:(NSString *) body {
    NSDate *now = [NSDate date];
    NSDateFormatter *df = [NSDateFormatter new];
    [df setDateFormat:@"YYYY-MM-dd"];
    NSDateFormatter *tf = [NSDateFormatter new];
    [tf setDateFormat:@"hh:mm"];
    DDXMLElement *postdate = 
           [DDXMLElement attributeWithName:@"postdate"
                    stringValue:[df stringFromDate:now]];
    DDXMLElement *posttime = 
           [DDXMLElement attributeWithName:@"posttime"
                    stringValue:[tf stringFromDate:now]];
    NSArray *attributes = 
         [NSArray arrayWithObjects:postdate, posttime, nil];
    DDXMLElement *subjectNode = 
         [DDXMLElement elementWithName:@"subject"
                          stringValue:subject];
    DDXMLElement *bodyNode = 
         [DDXMLElement elementWithName:@"body"
                          stringValue:body];
    NSArray *children = 
         [NSArray arrayWithObjects:subjectNode, bodyNode, nil];
    DDXMLElement *doc = 
        [DDXMLElement elementWithName:@"newsitem" 
                             children:children 
                           attributes:attributes];
    NSString *xml = [doc XMLString];
    return xml;
}

This version, if passed the same data as we used before, produces identical XML. So why go to the trouble of building up all those DOM structures when a simple format will do? Well, one reason is that using DOM makes sure that you don’t leave out a quotation mark or angle bracket, or forget an end tag, producing bad XML.

But there’s a much more important reason, which is the same reason you never use format strings to create SQL statements, it’s too easy to inject garbage into the payload. Let’s compare the two methods again, but with slightly more complex data (newlines and indents have been added to improve readability):

By Format: 
<newsitem postdate="2011-07-26" posttime="11:23">
   <subject>This is a test subject<subject>
   <body>
       Buggy whips are cool & neat, > all the rest, aren't they?
    </body>
</newsitem>

By DOM:
<newsitem postdate="2011-07-26" posttime="11:23">
   <subject>This is a test subject</subject>
   <body>
     Buggy whips are cool &amp; neat, &gt; all the rest, aren't they?
   </body>
</newsitem>

Oh my, look at all the illegal XML characters in the middle of our payload, just waiting to break any innocent XML parser waiting on the other end. By contrast, the example using DOM construction has appropriately escaped any dangerous content.

The decision as to whether to use formatting or DOM construction isn’t absolute. If you have good control of the data you’re sending, and can be absolutely sure that no XML-invalid characters will be contained inside the parameters, formatting can be a real time- and code-saver. But it is inherently more dangerous than using a DOM, and you also have to deal with pesky problems such as escaping all your quote signs. I’ve used both techniques where appropriate, but I’m starting to lean more heavily toward DOM construction these days.

One last item: once you have the XML, how do you send it in a POST? The code is fairly simple; it’s almost identical to the NSURLConnection code to do a GET. An example can be seen in Example 4-9.

Example 4-9. Posting data using NSURLConnection
-(void) sendNewsToServer:(NSString *) payload {
    NSURL *url = [NSURL URLWithString:@"http://thisis.a.bogus/url"];
    NSMutableURLRequest *request = [NSMutableURLRequest
                                    requestWithURL:url];
    [request setHTTPMethod:@"POST"];
    NSMutableDictionary *headers =
      [NSMutableDictionary dictionaryWithDictionary:[request allHTTPHeaderFields]];
    [headers setValue:@"text/xml" forKey:@"Content-Type"];
    [request setAllHTTPHeaderFields:headers];
    [request setTimeoutInterval:30];
    NSData *body = [payload dataUsingEncoding:NSUTF8StringEncoding];
    [request setHTTPBody:[NSMutableData dataWithData:body]];
    [NSURLConnection sendAsynchronousRequest:request
                                             queue:[NSOperationQueue currentQueue]
                           completionHandler:^(NSURLResponse *response,
                                                NSData *data, NSError *error) {
                               if (error != nil) {
                                   [self handleError:error.description];
                               } else {
                                   [self displayResponse:data];
                               }
                           }];
}

The only additional bits we need to do are to call setHTTPBody, add a content type using setAllHTTPHeaderFields, and use setHTTPMethod to indicate this is a POST. In all other ways, this acts just like a normal request, including the block handler and how response data is handled.

Once More, with JSON

Now that you know the basics of how to get XML back and forth to servers, it’s time to move on something a little more 21st century. One side effect of the rise of HTML5, AJAX, CSS, and all the other cool web technologies is that there are now a bunch of client-friendly JSON-speaking services out there that your application can leverage.

JSON is definitely the new kid on the block in the enterprise, and large companies are just starting to embrace it (my company has been using it in some of our newer products for about a year now). The big advantages that JSON brings are a lightweight syntax and easy integration into JavaScript. But how about iOS?

Again, until recently, I would have recommended a third-party library. There’s a perfectly serviceable third party library out there, called SBJSON. You can find it at https://github.com/stig/json-framework/ and once again, the site provides detailed instructions on how to install the package into your application. You’ll still need it if iOS 4 compatibility is an issue to you, but if you can make iOS 5 your minimum version, you can use the JSON support that’s now integrated into iOS.

We already have an HTTP connection framework in place, in the form of the NSURLConnection class, so all we’re really doing is changing how we generate and parse our payloads. Once again, our web service team is off taking long lunches, so we’ll have to test against a public service, in this case the GeoNames geographical names database. The GeoNames API takes all of its parameters on the URL, and returns a JSON payload as the response. So for the request http://api.geonames.org/postalCodeLookupJSON?postalcode=03038&country=AT&username=buggywhipco, you’ll get back a payload that looks like:

{"postalcodes":
  [
    {
     "adminName2":"Rockingham",
     "adminCode2":"015",
     "postalcode":"03038",
     "adminCode1":"NH",
     "countryCode":"US",
     "lng":-71.301971,
     "placeName":"Derry",
     "lat":42.887404,
     "adminName1":"New Hampshire"
     }
   ]
}

Basically, it’s an array of postal code records, with a name of fields inside it. To see how we can access this data in iOS, we’ll add another button to the tab bar, and add the code shown in Example 4-10 to the controller.

Example 4-10. Making a request that returns JSON
- (IBAction)lookupZipCode:(id)sender {
    NSURL *url = [WebServiceEndpointGenerator getZipCodeEndpointForZipCode:@"03038"
                                              country:@"US"
                                             username:@"buggywhipco"];
    NSURLRequest *request = [NSURLRequest requestWithURL:url];
    [NSURLConnection sendAsynchronousRequest:request
                                       queue:[NSOperationQueue currentQueue]
                           completionHandler:^(NSURLResponse *response,
                                                NSData *data, NSError *error) {
                               if (error != nil) {
                                   [self handleError:error.description];
                               } else {
                                   [self gotZipCode:data];
                               }
                           }];
}

- (void)gotZipCode:(NSData *)data
{
    NSError *error;
    NSDictionary *json = 
      [NSJSONSerialization JSONObjectWithData:data options:0 error:&error];
    if (error != nil) {
        [self handleError:error.description];
        return;
    }
        NSArray *postalCodes = [json valueForKey:@"postalcodes"];
        if ([postalCodes count] == 0) {
            NSLog(@"No postal codes found for requested code");
            return;
        }
        for (NSDictionary *postalCode in postalCodes) {
            NSLog(@"Postal Code: %@", [postalCode valueForKey:@"postalcode"]);
            NSLog(@"City: %@", [postalCode valueForKey:@"placeName"]);
            NSLog(@"County: %@", [postalCode valueForKey:@"adminName2"]);
            NSLog(@"State: %@", [postalCode valueForKey:@"adminName1"]);
            NSString *latitudeString =
                 [postalCode valueForKey:@"lat"];
            NSString *northSouth = @"N";
            if ([latitudeString characterAtIndex:0]== '-') {
                 northSouth = @"S";
                 latitudeString =
                     [latitudeString substringFromIndex:1];
                 }
            float latitude = [latitudeString floatValue];
            NSLog(@"Latitude: %4.2f%@",latitude, northSouth);
            NSString *longitudeString =
                 [postalCode valueForKey:@"lng"];
            NSString *eastWest = @"E";
            if ([longitudeString characterAtIndex:0]== '-') {
                 eastWest = @"W";
                 longitudeString =
                      [longitudeString substringFromIndex:1];
                 }
            float longitude = [longitudeString floatValue];
            NSLog(@"Longitude: %4.2f%@", longitude, eastWest);
    }
}

So, what have we got going on here? The request side is pretty straightforward: we generate a URL endpoint (the generator does pretty much what you’d expect it to do), and submit the request. When we get a response back, we check for error, and then use a method in the NSJSONSerialization class called JSONObjectWithData to parse the JSON.

The iOS 5 JSON implementation uses dictionaries, arrays, and strings to store the parsed JSON values. We look for the postalcodes value in the top level dictionary, which should contain an NSArray of postal codes. If we find it, we iterate over the array, and pluck out the individual values, printing them to the log. There’s even a little extra code to turn the latitude and longitude into more human-readable versions with E/W and N/S values.

There’s only one problem with the code as currently written: it will throw an exception when you run it. If you look closely at the original JSON, you’ll see that’s because the lat and lng values don’t have double quotes around them. As a result, the objects that will be put into the dictionary for them will be NSDecimalNumber objects, not NSString. Thus, all the string manipulation code will fail. We can fix the problem easily enough:

            NSDecimalNumber *latitudeNum = 
                 [postalCode valueForKey:@"lat"];
            float latitude = [latitudeNum floatValue];
            NSString *northSouth = @"N";
            if (latitude < 0) {
                northSouth = @"S";
                latitude = - latitude;
            }
            NSLog(@"Latitude: %4.2f%@", 
                latitude, northSouth);
            NSDecimalNumber *longitudeNum = 
                [postalCode valueForKey:@"lng"];
            float longitude = [longitudeNum floatValue];
            NSString *eastWest = @"E";
            if (longitude < 0) {
                eastWest = @"W";
                longitude = - longitude;
            }
            NSLog(@"Longitude: %4.2f%@", 
                 longitude, eastWest);

The reason that I made that deliberate mistake is to emphasize a point about JSON serialization, which is that you have to look closely at the shape of the JSON that gets returned, and know what to expect at any particular point in the tree. Running the corrected code generates good data in the log:

BuggyWhipChat[9811:b603] Postal Code: 03038
BuggyWhipChat[9811:b603] City: Derry
BuggyWhipChat[9811:b603] County: Rockingham
BuggyWhipChat[9811:b603] State: New Hampshire
BuggyWhipChat[9811:b603] Latitude: 42.89N
BuggyWhipChat[9811:b603] Longitude: 71.30W

Having to generate JSON is somewhat less frequent a task, but it’s equally simple to do. Simply use NSDictionary, NSArray, NSString, NSDecimalNumber and so on to construct the payload that you wish to send, then use the corollary method dataWithJSONObject of the NSJSONSerialization class, which will return the JSON NSData that corresponds to the structure.

SOAP on a Rope

So far, we’ve been dealing with modern, fairly light-weight web service protocols. Alas, in many enterprise companies, SOAP is still the name of the game. SOAP has a big advantage in that you can take a WSDL file and (theoretically) generate client bindings in a variety of languages and operating systems, and they will all be able to successfully communicate with the server.

The reality is somewhat less rosy. Things are certainly better than they were a few years ago, when getting a Java client to talk to an ASP.NET SOAP server could drive you insane. These days, the impedance mismatches of data types and incomplete data bindings are largely a thing of the past. What hasn’t changed is the God-awful bindings that you can end up with, especially if you annotate classes and let the framework generate the WSDL, as is the habit in many projects. And who can blame them, since WSDLs are one of the most unpleasant file specifications to author?

The reality of life, however, is that there’s a good chance you may need to consume a SOAP server as part of an iOS application. Luckily, there’s good tooling available to help. Just as there is wsdl2java to create Java bindings for SOAP, there’s a tool called WSDL2ObjC that will create all the classes to bind iOS applications. You can download it from http://code.google.com/p/wsdl2objc/, and unlike the other tools we’ve discussed in this section, it’s not a library but a Mac utility. When you download the tool and run it, you are presented with a window with two text fields and a button (Figure 4-4). You put a URI specification for a WSDL file in the first text field, the second specifies the directory that you want your generated code to be placed into. When you click the button, you get a directory full of files that you can add to your iOS project, and use to call the web service.

Generating a SOAP binding using WSDL2ObjC
Figure 4-4. Generating a SOAP binding using WSDL2ObjC

In this example, we’re consuming another weather service, because we really care about the weather here at BuggyWhipCo. An extra-humid day can ruin an entire batch of lacquered buggy whips, you know! In any event, we have the WSDL URI for a SOAP-based weather service, and we’ve used it to generate a whole bunch of files that support the binding to the service. If you look in the directory you generated your files into, you’ll see something like this:

NSDate+ISO8601Parsing.h
NSDate+ISO8601Parsing.m
NSDate+ISO8601Unparsing.h
NSDate+ISO8601Unparsing.m
USAdditions.h
USAdditions.m
USGlobals.h
USGlobals.m
WeatherSvc.h
WeatherSvc.m
xsd.h
xsd.m

With the exception of the two WeatherSvc files, everything else is standard and common to all SOAP web services you generate, and if you have more than one service you bind to, you’ll only need one copy of those files (if you generate all your services into the same directory, this should happen automatically).

Well, that was certainly easy! Except, actually, we’re just getting started. As anyone who has ever used SOAP bindings knows, making them work involves a bunch of guesswork and a menagerie of intermediate objects of no particularly obvious purpose. Let’s look at the code that you’d need to write in order to consume this service, shown in Example 4-11.

Example 4-11. Consuming a SOAP web service
- (IBAction)showSOAPWeather:(id)sender {
     WeatherSoapBinding *binding = 
         [WeatherSvc WeatherSoapBinding];
     binding.logXMLInOut = YES;
     WeatherSvc_GetWeather *params = [[WeatherSvc_GetWeather alloc] init];
     params.City = @"Derry, NH";
     WeatherSoapBindingResponse  *response = 
     [binding 
      GetWeatherUsingParameters:params];
     for (id bodyPart in response.bodyParts) {
         if ([bodyPart isKindOfClass:[SOAPFault class]]) {
             NSLog(@"Got error: %@", 
                   ((SOAPFault *)bodyPart).simpleFaultString);
             continue;
         }

         if ([bodyPart isKindOfClass:
              [WeatherSvc_GetWeatherResponse class]]) {
             WeatherSvc_GetWeatherResponse *response = bodyPart;
             NSLog(@"Forecast: %@", response.GetWeatherResult);
         }
     }
 }

The general pattern here, as with most SOAP bindings, is that you get a binding object (which has all the information about things such as the URI of the service). Then you get a request object, set all the parameters of the request, then call the appropriate method on the binding (in this case, GetWeatherUsingParameters), handing in the request. There are two versions of each request method, one synchronous (the one we’re using in this example), and one asynchronous (which uses the same kind of callback mechanism we’ve used in the JSON and XML examples). We also set the logXMLInOut flag to true, so we’ll get a look at what we’re sending and receiving in pure SOAP form, this can be very helpful figuring out where data is hiding in the response.

When the response returns, you have to iterate over the body parts, checking for SOAP faults. If the body part isn’t a SOAP fault, you have to cast it to the appropriate object. And here’s where the fun begins, because there’s nothing to tell you what the possible types are, except for diving through the generated code looking for where the elements are populated. But, if you do it all right, you’ll get results.

2011-11-18 08:16:17.365 BuggyWhipChat[69775:fb03] Forecast: Sunny

Looks like nice beach weather! If you’re getting the feeling that I’m not a big fan of SOAP, you wouldn’t be wrong. And, mind you, what we just saw is an extremely simple SOAP call, with no authentication and very simple request and response objects. I have not tried to make WSDL2ObjC work with WS-Security, although there is anecdotal evidence that it is possible. In any event, the good news is that SOAP on iOS isn’t impossible.

A Final Caution

Whenever you are relying on third-party libraries, you’re at the mercy of the maintainers. If it is a proprietary library, you have to hope that they will keep the tool up to date, and that it will not be broken by the next update to XCode or iOS.

For open source libraries, things are a little better. If the committers on the project aren’t willing or able to update the project, you have the source and can take a swing at it yourself. Depending on the license, this may mean that you have to make the sources to your changes available for download.

This is not some abstract warning, I know for a fact that most of the tools mentioned in the chapter do not work in projects that are using the Automatic Reference Count compiler feature in iOS 5. This means that you’re going to be faced with the choice of trying to get the libraries working with ARC, or building them as non-ARC static libraries that are called from your ARC project. One factor to consider when choosing libraries is how active the developer community is, and how often new versions come out. WSDL2ObjC, for example, seems relatively moribund, and might slip into orphan status in the future.

With our application cheerfully (if uselessly) talking to web services, we should think about how we’re going to test it. That’s what you’ll learn in Chapter 5.



[1] Note: Magic Unicorn Blood contains chemicals known to the state of California to cause cancer, birth defects, or other reproductive harm. Please consider the use of Magic Pixie Dust in place of Magic Unicorn Blood when feasible.

[2] Yes, I know, technically HTTP is the Application Layer, according to the OSI model. My apologies to pedantic taxonomists.

With Safari, you learn the way you learn best. Get unlimited access to videos, live online training, learning paths, books, interactive tutorials, and more.

Start Free Trial

No credit card required