Consuming XML Web Services in iPhone Applications

Consuming XML Web Services in iPhone Applications

Find out the various ways you can call web services and parseresponses from your iPhone applications to give your applications ahuge range of data sources.
by Wei-Meng Lee

Consuming <wbr>XML <wbr>Web <wbr>Services <wbr>in <wbr>iPhone <wbr>Applicationsommunicating with the outside world isone of the ways to make your iPhone applications interesting anduseful. This is especially true today where they are so many webservices that provide so much useful functionality. However,consuming web services in iPhone is not for the faint-of-heart.Unlike other development tools (such as Microsoft Visual Studio),Xcode does not have built-in tools that make consuming web serviceseasy. Everything must be done by hand and you need to know how toform the relevant XML messages to send to the web services and thenparse the returning XML result.

This article will give you a good understanding of how tocommunicate with XML web services from within your iPhoneapplication, and the examples will provide a solid foundation forconsuming other web services in your own projects.

Consuming Web Services

Consuming <wbr>XML <wbr>Web <wbr>Services <wbr>in <wbr>iPhone <wbr>Applications 
Figure 1.IPToCountry Web Service:This web service exposes two webmethods.

Before beginning an Xcode project to consume a web service,examining a real web service to see the different ways you canconsume is worthwhile. My favorite example is to use an ASMX XMLweb service created using .NET. For discussion purposes, here's alook at a web service called IPToCountry,which lets you supply an IP address and returns the country towhich the IP address belongs.

The IPToCountry webservice is locatedat http://www.ecubicle.net/iptocountry.asmx.If you use Safari to load this URL, you'll see that it exposes twoweb methods as shown in Figure1.

As an example,the FindCountryAsXML returnsthe result (the country) as an XML string. Clicking theFindCountryAsXML link reveals the page shownin Figure2.

Consuming <wbr>XML <wbr>Web <wbr>Services <wbr>in <wbr>iPhone <wbr>Applications 
Figure 2.FindCountryAsXml Service: Testing the webservice through a browser interface shows the XML packets exchangedbetween the client and the server.

The important parts are the sections following the Test sectionof the page. They detail the various ways in which you can consumethe web service. In the .NET world, accessing the web service is apretty straightforward affair—Visual Studio provides a built-intool that automatically creates a web proxy service object for theweb service when you download the WSDL document. For iPhonedevelopment, you need to get your hands dirty, so it's far moreimportant to understand the underlying mechanics of consuming webservices.

Using SOAP 1.1

One way to consume this web service is to use SOAP (SimpleObject Access Protocol). When using SOAP, you use theHTTPPOST method to send a request to theweb service, as shown in the following example:

POST /iptocountry.asmx HTTP/1.1
Host: www.ecubicle.net
Content-Type: text/xml; charset=utf-8
Content-Length: length
SOAPAction: "http://www.ecubicle.net/webservices/FindCountryAsXml"
<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
   xmlns:xsd="http://www.w3.org/2001/XMLSchema" 
   xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
  <soap:Body>
    <FindCountryAsXml xmlns="http://www.ecubicle.net/webservices/">
      <V4IPAddress>string</V4IPAddress>
    </FindCountryAsXml>
  </soap:Body>
</soap:Envelope>

The parts of the code in bold are the placeholders where youneed to substitute the actual values. A couple of important thingsto note from the above:

  • The URL for the web serviceis http://www.ecubicle.net/iptocountry.asmx.
  • The URL forthe SOAPAction attributeis http://www.ecubicle.net/webservices/FindCountryAsXml.
  • The Content-Type forthe request is text/xml;charset=utf-8.
  • The HTTP method is POST.
  • The SOAP request is shown below:
    <?xml version="1.0" encoding="utf-8"?>
    <soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
       xmlns:xsd="http://www.w3.org/2001/XMLSchema" 
       xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
      <soap:Body>
        <FindCountryAsXml xmlns="http://www.ecubicle.net/webservices/">
          <V4IPAddress>string</V4IPAddress>
        </FindCountryAsXml>
      </soap:Body>
    </soap:Envelope>
    
    
  • The Content-Length ofthe SOAP request is the total number of characters in the completeSOAP request.

The web service will return a response formatted as follows:

HTTP/1.1 200 OK
Content-Type: text/xml; charset=utf-8
Content-Length: length
<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
   xmlns:xsd="http://www.w3.org/2001/XMLSchema"
   xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
  <soap:Body>
    <FindCountryAsXmlResponse 
      xmlns="http://www.ecubicle.net/webservices/">
      <FindCountryAsXmlResult>xml result</FindCountryAsXmlResult>
    </FindCountryAsXmlResponse>
  </soap:Body>
</soap:Envelope>

For a real request, the result (the country) will be enclosedwithin the block of XML code (the bold placeholder in the precedingexample). You would need to extract it from the XML result.




Using SOAP 1.2

Using SOAP 1.2 is very similar to using SOAP 1.1. Here's asample request:

POST /iptocountry.asmx HTTP/1.1
Host: www.ecubicle.net
Content-Type: application/soap+xml; charset=utf-8
Content-Length: length
<?xml version="1.0" encoding="utf-8"?>
<soap12:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
  xmlns:xsd="http://www.w3.org/2001/XMLSchema" 
  xmlns:soap12="http://www.w3.org/2003/05/soap-envelope">
  <soap12:Body>
    <FindCountryAsXml xmlns="http://www.ecubicle.net/webservices/">
      <V4IPAddress>string</V4IPAddress>
    </FindCountryAsXml>
  </soap12:Body>
</soap12:Envelope>

The SOAP response for SOAP 1.2 would be:

HTTP/1.1 200 OK
Content-Type: application/soap+xml; charset=utf-8
Content-Length: length
<?xml version="1.0" encoding="utf-8"?>
<soap12:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
  xmlns:xsd="http://www.w3.org/2001/XMLSchema" 
  xmlns:soap12="http://www.w3.org/2003/05/soap-envelope">
  <soap12:Body>
    <FindCountryAsXmlResponse 
       xmlns="http://www.ecubicle.net/webservices/">
      <FindCountryAsXmlResult>xml result</FindCountryAsXmlResult>
    </FindCountryAsXmlResponse>
  </soap12:Body>
</soap12:Envelope>

Using HTTP GET

For many web services, if you do not want to use SOAP, you canoften use the simplerHTTP GET method. Here'sthe format to senda GET request:

GET /iptocountry.asmx/FindCountryAsXml?V4IPAddress=string HTTP/1.1
Host: www.ecubicle.net
HTTP/1.1 200 OK
Content-Type: text/xml; charset=utf-8
Content-Length: length

Take note of the following:

  • The URL for the web serviceis http://www.ecubicle.net/iptocountry.asmx/FindCountryAsXml?V4IPAddress=string.
  • The Content-Type forthe request is text/xml;charset=utf-8.
  • The Content-Length forthe request is 0, because you don't needto send anything separately (everything gets sent through the querystring).
  • The HTTP method is GET.

The web service will return its result in the followingformat:

<?xml version="1.0"?>
xml result

Using HTTP POST

In addition to using HTTP GET, you canalso use HTTP POST. Here's the format forsending the request:

POST /iptocountry.asmx/FindCountryAsXml HTTP/1.1
Host: www.ecubicle.net
Content-Type: application/x-www-form-urlencoded
Content-Length: length
V4IPAddress=string

Take note of the following:

  • The URL for the web serviceis http://www.ecubicle.net/iptocountry.asmx/FindCountryAsXml.
  • The Content-Type forthe requestis application/x-www-form-urlencoded.
  • The Content-Length forthe request is the lengthof V4IPAddress=string.
  • The HTTP method is POST.

The web service returns the following result:

HTTP/1.1 200 OK
Content-Type: text/xml; charset=utf-8
Content-Length: length
<?xml version="1.0"?>
xml result




Consuming Web Services in iPhone Applications

With all the background explained, you can now tackle theexciting task of consuming a web service in your iPhoneapplication! First, you need to learn to communicate with the webservice using SOAP.

Using Xcode, create a View-based Application project and nameit WebServices. Double-clickthe WebServicesViewController.xib fileto open it in Interface Builder. Double-click on the View item andpopulate it with the views listed below and shownin Figure3:

  • Label
  • Text Field
  • Round Rect Button
  • Activity Indicator
Consuming <wbr>XML <wbr>Web <wbr>Services <wbr>in <wbr>iPhone <wbr>Applications 
Figure 3.Building a Web Service Client: Populatethe View window with the various views shown here.

Back in Xcode, editthe WebServicesViewController.h fileand add the statements shown in bold in the following code:

#import <UIKit/UIKit.h>
@interface WebServicesViewController : UIViewController {
    //---outlets---
    IBOutlet UITextField *ipAddress;
    IBOutlet UIActivityIndicatorView *activityIndicator;
    
    //---web service access---
    NSMutableData *webData;
    NSMutableString *soapResults;
    NSURLConnection *conn;
}
@property (nonatomic, retain) UITextField *ipAddress;
@property (nonatomic, retain) UIActivityIndicatorView *activityIndicator;
- (IBAction)buttonClicked:(id)sender;
@end

Save the file and return to Interface Builder, where you need toperform the following actions:

  • Control-click on the File’s Owner item and drag it over theText Field view. Select ipAddress.
  • Control-click on the File’s Owner item and drag it over theActivity Indicator view.Select activityIndicator.
  • Control-click the Rounded Rect Button view and drag it over theFile’s Owner item. Selectthe buttonClicked: action.
Consuming <wbr>XML <wbr>Web <wbr>Services <wbr>in <wbr>iPhone <wbr>Applications 
Figure 4.Completed Connections:The figure shows the outletconnections and actions for the File’s Owner item.

If you right-click on the File’s Owner item now, you should seethe connections as shown in Figure4.

Inthe WebServicesViewController.m file,first create the setters and getters for the Text Field andActivity Indicator properties:

import "WebServicesViewController.h"
@implementation WebServicesViewController
@synthesize ipAddress;
@synthesize activityIndicator;

Next, definethe buttonClicked: method(see Listing1), which will formulate the SOAP request packet and send itover to the web service.

Let’s spend some time examining what you just did by adding thecode in Listing 1.First, you create the SOAP request packet:

    NSString *soapMsg = 
        [NSString stringWithFormat:
            @"<?xml version="1.0" encoding="utf-8"?>"
             "<soap:Envelope 
              xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
              xmlns:xsd="http://www.w3.org/2001/XMLSchema" 
              xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">"
             "<soap:Body>"
             "<FindCountryAsXml 
               xmlns="http://www.ecubicle.net/webservices/">"
             "<V4IPAddress>%@</V4IPAddress>"
             "</FindCountryAsXml>"
             "</soap:Body>"
             "</soap:Envelope>",  ipAddress.text
        ];

Next, you create a URL load request object using instances ofthe NSMutableURLRequest and NSURL objects:

    NSURL *url = [NSURL URLWithString: 
      @"http://www.ecubicle.net/iptocountry.asmx"];
    NSMutableURLRequest *req = 
      [NSMutableURLRequest requestWithURL:url];

You then populate the request object with the various headers,suchas Content-TypeSOAPAction,and Content-Length. You also set the HTTPmethod and HTTP body:

    NSString *msgLength = 
      [NSString stringWithFormat:@"%d", [soapMsg length]];
    [req addValue:@"text/xml; charset=utf-8"  
      forHTTPHeaderField:@"Content-Type"];
    [req addValue:@"http://www.ecubicle.net/webservices/FindCountryAsXml" 
      forHTTPHeaderField:@"SOAPAction"];
    [req addValue:msgLength 
      forHTTPHeaderField:@"Content-Length"];
    [req setHTTPMethod:@"POST"];
    [req setHTTPBody: [soapMsg dataUsingEncoding:NSUTF8StringEncoding]];

Before you make the actual request to the web service, you getthe Activity Indicator view to start animating, thus providing avisual feedback to the user that the application is waiting for aresponse from the web service:

[activityIndicator startAnimating];

To establish the connection with the web service, you use theNSURLConnection class together with the request object justcreated:

   conn = [[NSURLConnection alloc] initWithRequest:req delegate:self];
   if (conn) {
      webData = [[NSMutableData data] retain];
}    

The NSURLConnection object will now send the request to the webservice, and will asynchronously call various methods (which youwill define next) as it receives responses from the web service.The data method of theNSMutableData class returns an empty data object that represents awrapper for byte buffers, which you will use to receive incomingdata from the web service.

When data starts streaming in from the web service,the connection:didReceiveResponse: methodwill be called, which you need to implement here:

-(void) connection:(NSURLConnection *) connection 
didReceiveResponse:(NSURLResponse *) response {
   [webData setLength: 0];
}

Note that the preceding code initializes the lengthof webData to 0.As the data progressively comes in from the web service,the connection:didReceiveData:method willbe called repeatedly. You use the method to append the datareceived tothe webData object:

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

If there is an error during the transmission,the connection:didFailWithError: methodwill be called:

-(void) connection:(NSURLConnection *) connection 
didFailWithError:(NSError *) error {
[webData release];
    [connection release];
}

When the connection has finished and succeeded in downloadingthe response,the connectionDidFinishLoading: methodwill be called:

-(void) connectionDidFinishLoading:(NSURLConnection *) connection {
    NSLog(@"DONE. Received Bytes: %d", [webData length]);
    NSString *theXML = [[NSString alloc] 
                        initWithBytes: [webData mutableBytes] 
                        length:[webData length] 
                        encoding:NSUTF8StringEncoding];
    //---shows the XML---
    NSLog(theXML);
    [theXML release];    
    [activityIndicator stopAnimating];    
    
    [connection release];
    [webData release];
}

For this example, you simply print the XML response receivedfrom the web service to the Debugger Console window and then stopthe Activity Indicator view from animating.

Finally, release all the properties and objects inthe dealloc method:

- (void)dealloc {    
    [ipAddress release];
    [activityIndicator release];
    [xmlParser release];
    [soapResults release];
    [super dealloc];
}

That’s it! Press Command-R to test the application on the iPhoneSimulator. Enter the IPaddress 34.5.6.7 in theText Field, and then tap the Find Country button. In Xcode, pressShift-Command-R to open the Debugger Console window. You'll seethat the following request was sent to the web service:

<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope 
   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
   xmlns:xsd="http://www.w3.org/2001/XMLSchema" 
   xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
   <soap:Body>
      <FindCountryAsXml xmlns="http://www.ecubicle.net/webservices/">
         <V4IPAddress>34.5.6.7</V4IPAddress>
      </FindCountryAsXml>
   </soap:Body>
</soap:Envelope>

The web service SOAP response contains "United States."

<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope 
  xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" 
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
  xmlns:xsd="http://www.w3.org/2001/XMLSchema">
   <soap:Body>
      <FindCountryAsXmlResponse 
         xmlns="http://www.ecubicle.net/webservices/">
         <FindCountryAsXmlResult>
            <IPCountryService xmlns="">
               <Country>United States</Country>
            </IPCountryService>
         </FindCountryAsXmlResult>
      </FindCountryAsXmlResponse>
   </soap:Body>
</soap:Envelope>

The response from the web service indicates that you havemanaged to communicate with the web service. The problem now is howto parse the XML to extract the relevant result that you want. Inthis case, the result you want is encapsulated inthe <Country> element.The last section of this article shows how to parse the XMLresponse, but for now, let’s see what you should do if you want touse the HTTP GET and HTTP POST methods, as opposed to using theSOAP method to talk to the web service.




Using HTTP POST with Web Services

To talk to a web service using HTTP POST, you just need tomodifythe buttonClicked: method,like this:

- (IBAction)buttonClicked:(id)sender {
    NSString *postString = 
        [NSString stringWithFormat:@"V4IPAddress=%@", 
        ipAddress.text];
    NSLog(postString);
    
    NSURL *url = [NSURL URLWithString: 
        @"http:// www.ecubicle.net/iptocountry.asmx/FindCountryAsXml"];
    NSMutableURLRequest *req = [NSMutableURLRequest requestWithURL:url];
    NSString *msgLength = 
      [NSString stringWithFormat:@"%d", [postString length]];
    
[req addValue:@"application/x-www-form-urlencoded" 
    forHTTPHeaderField:@"Content-Type"];
[req addValue:msgLength forHTTPHeaderField:@"Content-Length"];
    [req setHTTPMethod:@"POST"];
    [req setHTTPBody: [postString 
       dataUsingEncoding:NSUTF8StringEncoding]];
    
    [activityIndicator startAnimating];
    
    conn = [[NSURLConnection alloc] initWithRequest:req delegate:self];
    if (conn) {
        webData = [[NSMutableData data] retain];
    }    
}

Basically, you do away with forming the SOAP message and simplypost a single string to the web service, suchas V4IPAddress=34.5.6.7. When you run themodified application and watch the Debugger Console, you should seea response like this:

<?xml version="1.0" encoding="utf-8"?>
<IPCountryService>
    <Country>United States</Country>
</IPCountryService>

Using HTTP GET withWeb Services

Using HTTP GET totalk to a web service is even simpler, because you pass all therequest information via the query string. The following shows themodifiedbuttonClicked: method to accessthe web service using theHTTP GET method:

- (IBAction)buttonClicked:(id)sender {
NSString *queryString = 
       [NSString stringWithFormat:
          @"http://www.ecubicle.net/iptocountry.asmx/" + 
          "FindCountryAsXml?V4IPAddress=%@", 
          ipAddress.text];
NSURL *url = [NSURL URLWithString:queryString];
NSMutableURLRequest *req = [NSMutableURLRequest requestWithURL:url];
[req addValue:@"text/xml; charset=utf-8" 
         forHTTPHeaderField:@"Content-Type"];
[req addValue:0 forHTTPHeaderField:@"Content-Length"];
[req setHTTPMethod:@"GET"];
[activityIndicator startAnimating];
conn = [[NSURLConnection alloc] initWithRequest:req delegate:self];
if (conn) {
webData = [[NSMutableData data] retain];
}
}

Like theHTTP POST method, theresponse in the Debugger Console when you run the modifiedapplication should look like this:

<?xml version="1.0" encoding="utf-8"?>
<IPCountryService>
    <Country>United States</Country>
</IPCountryService>

Parsing XML Responses

At this point, you're familiar with the various ways to call aweb service, now you need to learn how to parse the XML response.The iPhone SDK includes an NSXMLParser object that you can use toparse an XML file. The NSXMLParser class is an implementation ofthe Simple API for XML (SAX), which parses an XML document (orstring) serially.

An NSXMLParser object reads an XML document and scans it frombeginning to end. As it encounters the various items in thedocument (such as elements, attributes, comments, and so on), itnotifies its delegates so that you can take appropriate action,such as extracting the value of an element.

To parse the response from the web service, add the followingstatements tothe WebServicesViewController.h file:

#import <UIKit/UIKit.h>
@interface WebServicesViewController : UIViewController {
    //---outlets---
    IBOutlet UITextField *ipAddress;
    IBOutlet UIActivityIndicatorView *activityIndicator;
    
    //---web service access---
    NSMutableData *webData;
    NSMutableString *soapResults;
    NSURLConnection *conn;
    //---xml parsing---
    NSXMLParser *xmlParser;
    BOOL *elementFound;        
}
@property (nonatomic, retain) UITextField *ipAddress;
@property (nonatomic, retain) UIActivityIndicatorView *activityIndicator;
- (IBAction)buttonClicked:(id)sender;
@end

Inthe WebServicesViewController.m file,add the statements shown below in bold tothe connectionDidFinishLoading: method:

-(void) connectionDidFinishLoading:(NSURLConnection *) connection {
    NSLog(@"DONE. Received Bytes: %d", [webData length]);
    NSString *theXML = [[NSString alloc] 
                        initWithBytes: [webData mutableBytes] 
                        length:[webData length] 
                        encoding:NSUTF8StringEncoding];
    //---shows the XML---
    NSLog(theXML);
[theXML release];    
    [activityIndicator stopAnimating];    
    if (xmlParser)
    {
        [xmlParser release];
    }    
    xmlParser = [[NSXMLParser alloc] initWithData: webData];
    [xmlParser setDelegate: self];
    [xmlParser setShouldResolveExternalEntities:YES];
    [xmlParser parse];
    
    [connection release];
[webData release];
}

You create an instance of the NSXMLParser class and theninitialize it with the response returned by the web service. As theparser encounters the various items in the XML document, it willfire off several methods, which you need to define next.

The first method to implementis parser:didStartElement:namespaceURI:qualifiedName:attributes:,which is fired when the start tag of an element is found:

//---when the start of an element is found---
-(void) parser:(NSXMLParser *) parser 
   didStartElement:(NSString *) elementName 
namespaceURI:(NSString *) namespaceURI 
qualifiedName:(NSString *) qName
attributes:(NSDictionary *) attributeDict {
    
    if( [elementName isEqualToString:@"Country"])
    {
        if (!soapResults)
        {
            soapResults = [[NSMutableString alloc] init];
        }
        elementFound = YES;
    }
}

The preceding code checks to see whether the current tagis <Country>.If it is, you set the Booleanvariable elementFound to YES.

The next method to implementis parser:foundCharacters:,which gets fired when the parser finds the text of an element:

-(void)parser:(NSXMLParser *) parser foundCharacters:(NSString *)string
{
    if (elementFound)
    {
        [soapResults appendString: string];
    }
}

Here, if the last tag foundwas <Country>,you want to extract the value ofthe Country elementinto soapResults.

Finally, when the parser encounters the end of an element, itfiresthe parser:didEndElement:namespaceURI:qualifiedName: method:

//---when the end of element is found---
-(void)parser:(NSXMLParser *)parser 
    didEndElement:(NSString *)elementName 
    namespaceURI:(NSString *)namespaceURI 
    qualifiedName:(NSString *)qName
{
    if ([elementName isEqualToString:@"Country"])
    {
        //---displays the country---
        NSLog(soapResults);        
        UIAlertView *alert = [[UIAlertView alloc] 
          initWithTitle:@"Country found!" 

Consuming <wbr>XML <wbr>Web <wbr>Services <wbr>in <wbr>iPhone <wbr>Applications 
Figure 5.Country Found: The completed exampleapplication displays the country name after parsing the XMLresult.
                                                          
        message:soapResults 
        delegate:self  
        cancelButtonTitle:@"OK" 
        otherButtonTitles:nil];
        [alert show];
        [alert release];
        [soapResults setString:@""];
        elementFound = FALSE; 
    }
}

Here, you simply look for theclosing </Country> tag;when you find it you know that the value of the Country element hasbeen correctly extracted. You then print out the value using aUIAlertView object.

Finally! You can now test the application on the iPhoneSimulator by pressing Command-R. Enter an IP address and click theFind Country button. Figure5 shows you the result!

At this point, you've seen a working iPhone application thatillustrates the various ways you can consume a web service in youriPhone applications: SOAP, HTTP GET, andHTTP POST. The example both calls a webservice and shows how to extract data from the XML-formattedresponse. Using web services (and being able to parse XML) in youriPhone applications can open up an entire world full of data toyour applications.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值