Posts tagged ‘iphone’

April 28, 2011

Creating an asynchronous XML parser for iOS applications

If you are anything like me, and are a developer who has been working primarily with the .NET platform for the last little while, you may have a bit of a culture shock when coming to iOS development. I have worked with Objective C in the past, but primarily for OS X, and although the paradigms for programming are similar, the APIs available to iOS are slightly more limited. Subjectively, I have to say that Visual Studio is a far better IDE than Xcode – though Xcode has improved massively with version 4.

In this article I am going to explore how to write an XML parser that parses XML from a given URL asynchronously by implementing the NSXMLParserDelegate protocol and the NSURLConnectionProtocol, and using the NSXMLParser and NSURLConnection objects.

If you do not have a good grounding in Objective C, first please go and have a read of a few tutorials; if you have a decent grounding in an object oriented language, you should pick it up fairly quickly – though the memory management side of things may be confusing (and will probably be for a while!), unless you come from a non GC language background.

For the purpose of this exercise we are going to assume we are tackling a standard RSS XML feed (SlashDot RSS), that we want to represent as a collection of objects to be used in our application.

The first thing we need is an object that we will use for the representation of each item in the RSS.

We can create this as a new object, deriving from NSObject, with instance variables and @property’s for each of the RSS items.

The object’s header file would look something like:

@interface RSSItem : NSObject {
    NSString *title;
    NSString *description;

@property (nonatomic, retain) NSString *title;
@property (nonatomic, retain) NSString *description;


And its simple implementation file would like something like:

#import "RSSItem.h"

@implementation RSSItem

@synthesize title, description;

- (void)dealloc
    [title release];
    [description release];
    [super dealloc];


The above obviously only captures two of our RSS elements, but as a proof-of-concept will be fine, and can be extended to capture more elements.

Now that we have this defined, we can define our RSSParser. Our RSSParser can inherit directly from NSObject, but we will need to define it to comply to the NSXMLParserDelegate protocol, which allows us to set it to be the delegate object for an NSXMLParser object, so that it can deal with the elements therein.

Our header file for RSSParser would, therefore, look something like:

#import "RSSItem.h"

@interface RSSParser : NSObject <NSXMLParserDelegate> {
	//This variable will eventually (once the asynchronous event has completed) hold all the RSSItems in the feed
    NSMutableArray *allItems;
    //This variable will be used to map properties in the XML to properties in the RSSItem object
    NSMutableArray *propertyMap;
    //This variable will be used to build up the data coming back from NSURLConnection
    NSMutableData *receivedData;
    //This item will be declared and created each time a new RSS item is encountered in the XML
    RSSItem *currentItem;
    //This stores the value of the XML element that is currently being processed
    NSMutableString *currentValue;
    //This allows the creating object to know when parsing has completed
    BOOL parsing;
    //This internal variable allows the object to know if the current property is inside an item element
    BOOL inItemElement;

@property (nonatomic, readonly) NSMutableArray *allItems;
@property (nonatomic, retain) NSMutableArray *propertyMap;
@property (nonatomic, retain) NSData *receivedData;
@property (nonatomic, retain) RSSItem *currentItem;
@property (nonatomic, retain) NSMutableString *currentValue;
@property BOOL parsing;

//This method kicks off a parse of a URL at a specified string
- (void)startParse:(NSString*)url;


You will notice we have NOT specified that this object complies to the NSURLConnectionProtocol. The reason for this is that NSObject informally complies to this protocol by default, so we can add the necessary methods we need, and set this object as the delegate for the NSURLConnection object too.

So, when we first kick off a parse using the startParse: method, we will need to create an NSURLConnection object, which will kick off the retrieval of the data from the specified URL. Once this data is all retrieved we can then actually parse the XML.

We can use code similar to the following to achieve this:

- (void)startParse:(NSString *)url
    //Set the status to parsing
    parsing = true;

	//Initialise the receivedData object
    receivedData = [[NSMutableData alloc] init];
    //Create the connection with the string URL and kick it off
    NSURLConnection *urlConnection = [NSURLConnection connectionWithRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:url]] delegate:self];
    [urlConnection start];

As can be seen in our creation of the connection object, we have set the delegate to be the current object.

We can then define the methods we need to add the necessary data from the request as it comes back; and once it has all finished, to start the XML parser.

- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
	//Reset the data as this could be fired if a redirect or other response occurs
    [receivedData setLength:0];

- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
	//Append the received data each time this is called
    [receivedData appendData:data];

- (void)connectionDidFinishLoading:(NSURLConnection *)connection
	//Start the XML parser with the delegate pointing at the current object
    NSXMLParser *parser = [[NSXMLParser alloc] initWithData:receivedData];
    [parser setDelegate:self];
    [parser parse];
    parsing = false;

As can be seen, the implementation of this is very simple. In addition we should implement the method

- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error

to handle the failure of the connection request.

Finally, once we have done this we can define the methods for the NSXMLParserDelegate protocol, so that the item is built and added to allItems correctly.

We can do this with code similar to the following:

- (void)parserDidStartDocument:(NSXMLParser *)parser
	//Create the property map that will be used to check and populate from elements
    propertyMap = [[NSMutableArray alloc] initWithObjects:@"title", @"description", nil];
    //Clear allItems each time we kick off a new parse
    [allItems removeAllObjects];

- (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName attributes:(NSDictionary *)attributeDict
	//If we find an item element, then ensure that the object knows we are inside it, and that the new item is allocated
    if ([elementName isEqualToString:@"item"])
        currentItem = [[RSSItem alloc] init];
        inItemElement = true;

- (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName
	//When we reach the end of an item element, we should add the RSSItem to the allItems array
    if ([elementName isEqualToString:@"item"])
        [allItems addObject:currentItem];
        [currentItem release];
        currentItem = nil;
        inItemElement = false;
    //If we are in element and we reach the end of an element in the propertyMap, we can trim its value and set it using the setValue method on RSSItem
    if (inItemElement)
        if ([propertyMap containsObject:elementName])
            [currentItem setValue:[currentValue stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]] forKey:elementName];
    //If we've reached the end of an element then we should the scrap the value regardless of whether we've used it
    [currentValue release];
    currentValue = nil;

- (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string
	//When we find characters inside an element, we can add it to the current value, which is created if it is not initialized at present
    if (!currentValue)
        currentValue = [[NSMutableString alloc] init];
    [currentValue appendString:string];

As can be seen again, this implementation is very simple, but requires a slight degree of lateral thinking, compared to using a DOM based parser, as most programmers from a .NET background would be familiar with.