NSXMLParser

Modified

Resources

NSXMLParser Delegate Protocol Reference

Download

iTunes Top 10 example

Overview

SAX is an asynchronous parser of XML, NSXMLParser is a SAX supplied by Apple.

SAX generates asynchronous events for the start and end of the document, elements and text characters, often requiring only memory for a single element rather than the entire document. The result is often faster parsing and much smaller memory requirements. SAX is appropriate where the entire tree is not required at one time. The advantage for mobile devices with the SAX approach is that the DOM tree size is directly proportional to the XML document size, for large XML documents, memory requirements may be too great; where DOM requires the entire document to be processed, SAX can locate the elements of interest and terminate downloading the complete document.

Example - iTunes Top 10

iTunes publishes the ranked listing of downloaded tunes as RSS, an XML data structure commonly used for news feeds, weather, blogs, etc.

The application parses the RSS to locate the <title> element. The relevant parts of the RSS feed are listed below.

<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"xmlns:dc="http://purl.org/dc/elements/1.1/"
        xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
        xmlns:content="http://purl.org/rss/1.0/modules/content/"
        xmlns:itms="http://phobos.apple.com/rss/1.0/modules/itms/">
  <channel>
    <title>iTunes Top 10 Songs</title>
    <link>http://itunes.apple.com/WebObjects/MZStore.woa/wa/viewTop?id=1&amp;popId=1</link>
    <description>iTunes Store: Today's Top 10 Songs</description>
    <image>
        <url>/images/rss/badge.gif</url>
        <link>http://www.apple.com/itunes/</link>
        <title>iTunes Music Store</title>
        <height>31</height><width>88</width>
    </image>
    <item>
       <title>1. My Life Would Suck Without You - Kelly Clarkson</title>
       <title>2. Gives You Hell - The All-American Rejects</title>
                                :

The iTunes URL to return the top 10 songs is:

Key Parsing Methods using NSXMLParser

NSXMLParser methods

Delegate methods

Called when an event occurs, as their name indicates. Note the -foundCharacters: discussion below.

 XMLParser class

1. receives the iTunes URL
2. downloads and parses the XML
3. appends the text between <title> </title> to a string
4. when parsing completed, returns the string of all title's text.

@interface XMLParser : NSObject  <NSXMLParserDelegate>
{
	NSString		*current;
	NSMutableString	*outstring;
}
- (NSString *)parseXMLFile: (NSURL *) url;
@end
#import "XMLParser.h"

@implementation XMLParser

- (NSString *)parseXMLFile: (NSURL *) url
{	
	outstring = [[NSMutableString alloc] init];
	
    	NSXMLParser *parser = [[NSXMLParser alloc] initWithContentsOfURL: url];

    	[parser setDelegate: self];
	[parser parse];
    	[parser release];
	
	return [outstring autorelease];
}
//  <title>iTunes Top 10 Songs</title>
- (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName 
                                   		      namespaceURI:(NSString *)namespaceURI 
					      qualifiedName:(NSString *)qName 
					      attributes:(NSDictionary *)attributeDict   
{
    	if (qName) elementName = qName;
	if (elementName) current = [NSString stringWithString:elementName];
}

- (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName 
					     namespaceURI:(NSString *)namespaceURI 
					     qualifiedName:(NSString *)qName
{
	current = nil;
}

- (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string
{
	if (!current) return;
	if ([current isEqualToString:@"title"]) 
		[outstring appendFormat:@"%@\n", string];
}
//  <title>iTunes Top 10 Songs</title>
-(void) parserDidStartDocument:(NSXMLParser *)parser {
	NSLog(@"parserDidStartDocument"); 	
}

-(void) parserDidEndDocument: (NSXMLParser *)parser {
	NSLog(@"parserDidEndDocument %@", outstring); 	
}
@end
<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"xmlns:dc="http://purl.org/dc/elements/1.1/"
        xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
        xmlns:content="http://purl.org/rss/1.0/modules/content/"
        xmlns:itms="http://phobos.apple.com/rss/1.0/modules/itms/">
  <channel>
    <title>iTunes Top 10 Songs</title>
    <link>http://itunes.apple.com/WebObjects/MZStore.woa/wa/viewTop?id=1&amp;popId=1</link>
    <description>iTunes Store: Today's Top 10 Songs</description>
    <image>
        <url>/images/rss/badge.gif</url>
        <link>http://www.apple.com/itunes/</link>
        <title>iTunes Music Store</title>
        <height>31</height><width>88</width>
    </image>
    <item>
       <title>1. My Life Would Suck Without You - Kelly Clarkson</title>
       <title>2. Gives You Hell - The All-American Rejects</title>
                                :

iTunesTop10ViewController

The application performs no XML-specific functions, other than calling the XMLParser constructor and defining the iTunes URL.

Is the main thread executing?

The following blocks execution.

top10TextView.text = [xmlParser parseXMLFile: url];

#import <UIKit/UIKit.h>
#import "XMLParser.h"

@interface iTunesTop10ViewController : UIViewController {
	XMLParser *xmlParser;
	IBOutlet UITextView *top10TextView;
}
-(IBAction) loadButton: (id) send;
@end
#import "iTunesTop10ViewController.h"

@implementation iTunesTop10ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    top10TextView.text = @"";
}

-(IBAction) loadButton: (id) send {
	xmlParser = [XMLParser new];
	
	NSURL *url = [NSURL URLWithString:
                @"http://ax.phobos.apple.com.edgesuite.net/WebObjects/MZStore.woa/wpa/MRSS/topsongs/limit=10/rss.xml"];
	
	top10TextView.text = [xmlParser parseXMLFile: url]; 

	[xmlParser release];
}
@end

 

Problems with Parsing and Other Time-Consuming Operations OR Why Threads are Important

Downloading and processing large files is one example of a time-consuming operation, when executed in the main thread, the UI is essentially dead until the operation is completed.

The general solution is to execute time-consuming operations in a separate thread and notify the main thread when the operation has completed.

iTunes Top 10 example has been refactored for threading and event notification through a delegate.

Top10Parser

The Top10Parser class runs a thread separate from the main or UI thread. A good question is why, given that NSXMLParser generates call-back events to NSXMLParserDelegate methods implemented in Top10Parser?

The answer has to do with whose thread is executing the delegate methods and how much time is spent in executing those methods.

Delegate methods are expected to execute in the delegate's thread, so we would expect NSXMLParserDelegate methods to execute in whatever thread was executing Top10Parser.

When the time spent in each execution of a delegate method is small and the number of executions is few, the main or UI thread could handle the executions without unreasonable delays.

When the time spent in each execution of a delegate method is large or the number of executions is many, the main or UI thread may not handle the executions without unreasonable delays. A separate thread should be execute the delegate methods.

Running the parser in a separate thread is simple enough. However, when the parsing is complete, how to notify the UI thread that results are available for updating the view?

Top10Parser initializes with -initWithDelegate:url: method, the delegate's thread is copied for calling the delegate method.

The delegate method, -(void) parseDidComplete: (NSMutableString *) result;, is then called on the delegate's thread. If the delegate is the ViewController, the thread is the UI.

The call-back code to the delegate on the delegate's thread is:

[delegate performSelector:@selector(parseDidComplete:) onThread: thread withObject: outstring waitUntilDone:NO];

The XMLParser class has been refactored to Top10Parser, adding:

  1. 1. delegate instance variable
    2. thread instance variable for delegate's thread,
  2. 3. -initWithDelegate:url: method to set the delegate, start a thread on the -run: method
  3. 4. -run: method that downloads and parses the iTunes XML, calling the -parseDidComplete: delegate method on the delegates thread when parse completed, and passing the parse result as a string.

Comparing the XMLParser with the Top10Parser.

Top10Parser
@interface Top10Parser : NSObject  <NSXMLParserDelegate>
{
	NSString		*current;
	NSMutableString	*outstring;
	NSURL			*url;
	id			delegate;
	NSThread		*thread;
           NSThread        	*myThread;
}

- (Top10Parser *) initWithDelegate: (id) d url: (NSURL *) u;
@end
#import "Top10Parser.h"

@implementation Top10Parser

- (Top10Parser *) initWithDelegate: (id) d url: (NSURL *) u {
	self = [super init];
	delegate = d;
    	url = [u retain];

	thread = [NSThread currentThread];				// Assumed delegate's thread
    
	outstring = [[NSMutableString alloc] init];
	
	myThread = [[NSThread alloc] initWithTarget:self selector:@selector(run:) object: nil];
    	[myThread start];
	return self;
}

- (void) run: (id) param  {
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
	
    NSXMLParser *parser = [NSXMLParser alloc];
    [parser initWithContentsOfURL: url];
    [parser setDelegate: self];
    [parser parse];
    [parser release];

    if ([delegate respondsToSelector:@selector(parseDidComplete:)])
        [delegate performSelector:@selector(parseDidComplete:) onThread: thread 
                       withObject: outstring waitUntilDone:NO];

    [pool release];
} 

- (void) dealloc  {
	[outstring release];
    	[myThread release];
    	[url release]
	[super dealloc];
}

- (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName 
				namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName 
				attributes:(NSDictionary *)attributeDict  {
    if (qName) elementName = qName;
    if (elementName) current = [NSString stringWithString:elementName];
}

- (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName 
			namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName {
	if ([current isEqualToString:@"title"]) [outstring appendFormat:@"%@", @"\n"];
	current = nil;
}

- (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string {
	if (!current) return;
	if ([current isEqualToString:@"title"]) [outstring appendFormat:@"%@", string];
}

-(void) parserDidEndDocument:(NSXMLParser *)parser {}
-(void) parserDidStartDocument:(NSXMLParser *)parser {}

@end

iTunesTop10ViewController

Primarily refactoring has added:

1. UIActivityIndicatorView outlet to observe UI still functional during parsing.
2. -parseDidComplete: delegate method to receive parse completed event and update the UI.

Download and parsing now are performed in a separate thread from the main thread.

When parsing completed the parsing thread calls -parseDidComplete: delegate method on the delegate's thread.

The -parseDidComplete: delegate method updates the application's UITextView so the user can see the Top 10 list.

iTunesTop10ViewController
#import <UIKit/UIKit.h>
#import "Top10Parser.h"

@interface iTunesTop10ViewController : UIViewController {
	IBOutlet UITextView *top10TextView;
	IBOutlet UIActivityIndicatorView *activityIndicator;
}
-(void) parseDidComplete: (NSMutableString *) result;

-(IBAction) loadButton: (id) send;
@end
#import "iTunesTop10ViewController.h"
#import "Top10Parser.h"

@implementation iTunesTop10ViewController

Top10Parser * top10Parser;

-(IBAction) loadButton: (id) send {
	top10TextView.text = @"";
	[activityIndicator startAnimating];
		
	NSURL *url = [NSURL URLWithString:
                 @"http://ax.phobos.apple.com.edgesuite.net/WebObjects/MZStore.woa/wpa/MRSS/topsongs/limit=10/rss.xml"];
	
          top10Parser = [[Top10Parser alloc] initWithDelegate: self url: url];}	
}

-(void) parseDidComplete: (NSMutableString *) result {
	top10TextView.text = result; 
	[activityIndicator stopAnimating];
	[top10Parser release];
}
@end