Archive

Posts Tagged ‘Objective C’

Using SWS on iPhone/iPad – Part 2 Parsing the XML response

SAX vs. DOM

As we begin, I want to explain the most important difference between XML parsers, whether the parser is a SAX or a DOM parser.
  • A SAX parser is one where your code is notified as the parser walks through the XML tree, and you are responsible for keeping track of state and constructing any objects you might want to keep track of the data as the parser marches through.
  • A DOM parser reads the entire document and builds up an in-memory representation that you can query for different elements. Often, you can even construct XPath queries to pull out particular pieces.
Alright, now let’s discuss some of the libraries!

XML Parsers for the iPhone

In the iphone SDK the two choices are:
  • NSXMLParser is a SAX parser included by default with the iPhone SDK. It’s written in Objective-C and is quite straightforward to use, but perhaps not quite as easy as the DOM model.  It is the fastest parser in terms of parsing xml from start to finish.
  • libxml2 is an Open Source library that is included by default with the iPhone SDK. It is a C-based API, so is a bit more work to use than NSXML. The library supports both DOM and SAX processing. The libxml2 SAX processor is especially cool, as it has a unique feature of being able to parse the data as it’s being read. For example, you could be reading a large XML document from the network and displaying data that you’re reading for it to the user while you’re still downloading. Therefore, while this parser is slower than NSXMLParser from start to finish, the user experience is improved in the case of large xml documents.
If you’re wondering about memory usage, it’s a wash if you’re comparing SAX parsers.  If you implement DOM parser you will incur a greater memory usage.  There are other 3rd party libraries
which provide other DOM capabilities such as XPath functionality.  If you get into parsing complex xml structures these may be a good consideration. For our purposes we will use NSXMLParser.  I’ve found it to be the path of least resistance and it really wants to be friendly so we’ll play along.

The XML

As we are calling the site list method we will be getting back xml that follows the format documented at: http://host05slc.centershift.com/iService/iService1.asmx?op=GetSiteList
Here’s the response format:
xml version=”1.0″ encoding=”utf-8″?>
<soap:Body>
<GetSiteListResponse xmlns=”http://centershift.com/”&gt;

<GetSiteListResult>

<Facility>

<SiteName>string</SiteName>

<SiteId>string

<DistanceTo>string</DistanceTo>

</Facility>

<Facility>

<SiteName>string</SiteName>

<SiteId>string</SiteId>

<DistanceTo>string</DistanceTo>

</Facility>

</GetSiteListResult>

</GetSiteListResponse>

</soap:Body>
</soap:Envelope>
We want to parse through all the ‘Facility’ elements and grab the SiteName and SiteId to display them in a list.  To launch the parsing process we will want to add some code to the connectionDidFinishLoading method.
– (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
//Manipulate data here and release Data and Connection
NSLog(@”Succeeded!
Received %d bytes of data\n”,[webData length]);
NSString *theXml = [[NSString alloc]initWithBytes:[webData mutableBytes] length:[webData length] encoding:NSUTF8StringEncoding];
[theXml release];
// Parse received xml from web service
// XMLParse declared in the header
myParser= [[NSXMLParser alloc] initWithData: webData];
[myParser setDelegate:self];
// Depending on the XML document you’re parsing, you may want to enable these features of NSXMLParser.
[myParser setShouldProcessNamespaces:NO];
[myParser setShouldReportNamespacePrefixes:NO];
[myParser setShouldResolveExternalEntities:NO];
[myParser parse];
// release the connection, and the data object
[connection release];
[webData release];
}
Now it’s time to delve into the parser object methods.  First let’s deal with handling errors thrown during the parse process with the parseErrorOccurred method.
– (void)parser:(NSXMLParser *)parser parseErrorOccurred:(NSError *)parseError
{
NSString * errorString = [NSString stringWithFormat:@”Unable to download feed from web service (Error code %i )”, [parseError code]];
NSLog(@”error parsing XML: %@”, errorString);
UIAlertView * errorAlert = [[UIAlertView alloc] initWithTitle:@”Error loading content” message:errorString delegate:self
cancelButtonTitle:@”OK” otherButtonTitles:nil];
[errorAlert show];
}
At this point in the didStartElement method we will do our work to clear out the item cache and initialize a new one when we find the start of the ‘Facility’ element.
-(void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName

 

namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName attributes:(NSDictionary *)attributeDict

 

{
currentElement=[elementName copy];
if
([elementName isEqualToString:@”Facility”])
{
[currentSite release];
currentSite = [[Site alloc] init];
}}
As the parser parses the Facility element we will append the strings to the two items in our ‘currentSite’ object, namely ‘siteName’ and ‘siteId’.
– (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string
{
// save the characters for the current item…
if
([currentElement isEqualToString:@”SiteName”])
{
currentSite.siteName = [currentSite.siteName stringByAppendingString:string];
}
else if
([currentElement isEqualToString:@”SiteId”])
{
currentSite.siteId = [currentSite.siteId stringByAppendingString:string];
}}
The next method lets us deal with whatever we want to do once the parser finds the end element.  In this case we will add the object ‘currentSite’ to our collection or array of objects  ‘allSites’.
– (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName
{
if
([elementName isEqualToString:@”Facility”])
{
// save values to an item, then store that item into the array…
[allSites addObject:currentSite];
}}
Lastly, when the parser reaches the end of the xml document we’ll want to perform whatever garbage collection we need to do.  Also, here I allocate a new array of the Sitelist and  load the data into a table which is displayed in the view.  It’s also a good point to stop the animation of the busy Signal so the user has a good experience.
– (void)parserDidEndDocument:(NSXMLParser *)parser
{
// set mySites to allSites
[mySites release];
mySites = [[NSArray alloc] initWithArray:allSites];
[sitelistTable reloadData];
// Stop Indicator
[busySignal stopAnimating];
}

Using SWS web services on iPhone/iPad – Part 1 Calling the service

iPhone/iPad Apps

In today’s marketplace users are looking for applications that extend their functionality to their iPhone/iPad device.  Building business applications on the iPhone/iPad that communicating with web services that provides that functionality in the cloud is a great way to embrace this build forward thinking revolution.  However, consuming web services in an iPhone application 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 services easy. Everything must be done by hand and you need to know how to form the relevant XML (SOAP) messages to send to the web services and then parse the returning XML result.

Using SOAP 1.1

One way to consume this web service is to use SOAP (Simple Object Access Protocol). When using SOAP, you use the HTTP POST (or securely over HTTPS) method to send a request to the web service.  In a browser open the url: http://host05slc.centershift.com/iService/iService1.asmx The following is a sample SOAP 1.1 request and response.
The placeholders shown need to be replaced with actual values.
POST /iService/iService1.asmx HTTP/1.1
Host: host05slc.centershift.com
Content-Type: text/xml; charset=utf-8
Content-Length: length
SOAPAction: "http://centershift.com/GetSiteList"

      string</UserName>
      <Password>string
    <!--GetSiteList>

To get a request to work from within xCode/Objective C

-(void)GetSiteList{
NSString *soapMessage = [NSString stringWithFormat:@
“xml version=\”1.0\” encoding=\”utf-8\”?>\n”
“<soap:Body>\n”
“xmlns=\”http://centershift.com/\”>\n”
“<UserName>%@</UserName>\n”
“<Password>%@</Password>\n”
“</GetSiteList>\n”
“</soap:Body>\n”
“</soap:Envelope>\n”, [[NSUserDefaults standardUserDefaults] stringForKey:@”MyCSUserName”], [[NSUserDefaults standardUserDefaults] stringForKey:@”MyCSPassword”] ];
// Web service I setup to do this test application.
NSURL *url = [NSURL URLWithString:@”http://host05slc.centershift.com/iService/iService1.asmx“];
// Setup the web request with the url we created
NSMutableURLRequest *theRequest = [NSMutableURLRequest requestWithURL:url];
// need length of the soap xml for the header in the html file

NSString *msgLength = [NSString stringWithFormat:@”%d”, [soapMessage length]];
// add our htm headers which is a must for a WebService
[theRequest addValue:@”text/xml; charset=utf-8″ forHTTPHeaderField:@”Content-Type”];
[theRequest addValue:@”http://centershift.com/GetSiteList&#8221; forHTTPHeaderField:@”SOAPAction”];
[theRequest addValue:msgLength forHTTPHeaderField:@”Content-Length”];
//We are doing a HTML POST
[theRequest setHTTPMethod:@”POST”];
// set the HTML Body, which is the XML Soap message we created above. Needs to be UTF8 encoding
// Needs to be of type Data [theRequest setHTTPBody: [soapMessage dataUsingEncoding: NSUTF8StringEncoding]];
// create the connection with the request
// and start loading the data, also set delegate to self
NSURLConnection *theConnection=[[NSURLConnection alloc] initWithRequest:theRequest delegate:self];
if (theConnection)
{
// Create the NSMutableData that will hold
webData=[[NSMutableData data] retain];
}
else
{
// inform the user that the download could not be made
NSLog(@”ERROR”);
}
}

That’s the essence to format the SOAP message and post the request.  In Objective C you’ll need to add the following for the methods of the NS objects we use:

– (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
{
// this method is called when the server has determined that it
// has enough information to create the NSURLResponse
// clear any data that may be around.
[webData setLength:0];
}
– (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
{
// append the new data to the webData
// webData is declared in the header file
[webData appendData:data];
}
– (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
{
// Insert your code here to handle the problems created by connection failing with error
// Some typical things to handle:
// Release the connection, and the data object
[connection release];
[webData release];
// Display Alert UIAlertView
*errorNoConnection = [[UIAlertView alloc] initWithTitle:@”No Internet Connection” message:@”Unable to load site list” delegate:self cancelButtonTitle:@”OK” otherButtonTitles:nil];
[errorNoConnection show];
[errorNoConnection autorelease];
// Stop Indicator
[busySignal stopAnimating];
}
– (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
//Manipulate data here and release Data and Connection
NSLog(@”Succeeded! Received %d bytes of data\n”,[webData length]);
NSString *theXml = [[NSString alloc]initWithBytes:[webData mutableBytes] length:[webData length] encoding:NSUTF8StringEncoding];
[theXml release];
// Parse received xml from web service
// We’ll insert some logic here to parse the XML in Part 2 of this post
// release the connection, and the data object
[connection release];
[webData release];
}

This is a pretty vanilla service to call but all basic nuts and bolts are all there to call a web service in Objective C.  In part 2 we’ll look at what we need to do to parse the xml and do something useful with the data.  Please feel free to comment or email me with any questions or suggestions you’d like to make in relation to this post.