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&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
- NSXMLParser *parser = [[NSXMLParser alloc] initWithContentsOfURL: url]; constructs a parser that loads XML from a given URL.
- [parser setDelegate: self]; sets self as the delegate.
- [parser parse]; parses the XML.
Delegate methods
Called when an event occurs, as their name indicates. Note the -foundCharacters: discussion below.
- - (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName
namespaceURI:(NSString *)namespaceURI
qualifiedName:(NSString *)qName
attributes:(NSDictionary *)attributeDict
- - (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName
namespaceURI:(NSString *)namespaceURI
qualifiedName:(NSString *)qName
- - (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string
An element containing many characters, such as: <title>Little Fugue in G-Minor</title> may result in multiple calls, necessary to concatenate the received string parameter until the -didEndElement event signals the complete text has been received.
- -(void) parserDidStartDocument:(NSXMLParser *)parser {
- -(void) parserDidEndDocument: (NSXMLParser *)parser {

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];
}
- (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];
}
-(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&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. delegate instance variable
2. thread instance variable for delegate's thread,- 3. -initWithDelegate:url: method to set the delegate, start a thread on the -run: method
- 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.


@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.
#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
|