In my blog post found at last post, I explored ways to optimize Ray Wenderlich’s’s XML parser comparison for maximum performance. The results showed a significant speed advantage, roughly twice as fast as TBXML (the closest competitor) and seven times faster than Apple’s NSXMLParser.
While the final code is available on here, I haven’t detailed the process behind it. Initially, I obtained both Ray’s project and Objective-XML 5.3. Creating a reusable library for iPhone OS presented challenges due to the lack of support for frameworks and dynamic libraries. Additionally, the simulator and device environments differ not only in Xcode architecture and SDKs but also represent entirely distinct platforms. A method for creating a single target that compiles and links for both remains elusive—any insights would be greatly appreciated!
Compiling for iPhone OS requires selecting the iXmlKit target, as depicted below:

Two separate builds are necessary: one for the device and one for the simulator, each with its respective Active SDK setting. This generates two libiXmlKit.a libraries situated in Release-iphoneos and Release-iphonsimulator directories (relative to your build directory), as demonstrated:
1
| marcel@mpwls\[LD\]ls -la ~/programming/Build/Release-iphoneos/
|
-rw-r–r– 1 marcel staff 521640 May 9 19:52 libiXmlKit.a
1
2
|
These two libraries then need merging into a single, universal library using the 'lipo' command. This ensures compatibility with both the simulator and the device.
|
lipo -create Release-iphoneos/libiXmlKit.a Release-iphonesimulator/libiXmlKit.a -output Release/libiXmlKit.a
1
2
3
4
5
6
|
Newer Objective-XML versions streamline this with a dedicated shell-script target. After generating the combined libiXmlKit.a library, I added a new MAX group within the XMLPerformance project, placing both the library and the MAX header file into this group:

Next, I created a new Song parser class named MAXSongParser:
|
#import “iTunesRSSParser.h”
@interface MAXSongParser : iTunesRSSParser {
idparser;
NSDateFormatter *parseFormatter;
}
@end
1
2
|
The implementation is fairly straightforward. The init method initializes the MAX parser:
|
#import “MAXSongParser.h”
#import “MPWMAXParser.h”
#import “Song.h”
@implementation MAXSongParser
-init
{
self=[superinit];
parseFormatter = [[NSDateFormatteralloc] init];
[parseFormattersetDateStyle:NSDateFormatterLongStyle];
[parseFormattersetTimeStyle:NSDateFormatterNoStyle];
MPWMAXParser *newParser=[MPWMAXParserparser];
[newParser setUndefinedTagAction:MAX_ACTION_NONE];
[newParser setHandler:selfforElements:[NSArray arrayWithObjects: @“item”,@“album”,@“title”,@“channel”,@“rss”,@“category”,nil]
inNamespace:nilprefix:@““map:nil];
[newParser setHandler:selfforElements:[NSArrayarrayWithObjects:
@“releasedate”,@“artist”,@“album”,nil]
inNamespace:@“http://phobos.apple.com/rss/1.0/modules/itms/"
prefix:@““map:nil];
parser=[newParser retain];
returnself;
}
1
2
3
4
5
6
|
This initialization specifies the elements of interest using two "setHandler:forElements:inNamespace:prefix:map:" messages, one for each namespace. The default (RSS) namespace targets "item", "album", "title", "channel", "rss", and "category". For Apple's "itms" namespace, we handle "releasedate", "artist", and "album". Setting MAX\_ACTION\_NONE instructs the parser to disregard undefined elements and their children.
Song object creation takes place in the -itemElement:... method, mapping child elements to Song attributes:
\-itemElement:children attributes:attributes parser:parser
|
{
1
| Song \*song=\[\[Songalloc\] init\];
|
[song setAlbum:[children objectForUniqueKey:@“album”]];
1
| \[song setTitle:\[children objectForUniqueKey:@"title"\]\];
|
[song setArtist:[children objectForUniqueKey:@“artist”]];
1
| \[song setAlbum:\[children objectForUniqueKey:@"album"\]\];
|
[song setCategory:[children objectForUniqueKey:@“category”]];
1
| \[song setReleaseDate:\[parseFormatterdateFromString:\[children objectForUniqueKey:@"releasedate"\]\] \];
|
return song;
1
2
3
4
5
6
7
| }
```
Two additional methods finalize the parsing process. Since 'channel' elements can have multiple 'item' children, we retrieve them using "objectsForKey:":
```
\-channelElement:children attributes:attributes parser:parser
|
{
1
| return \[\[children objectsForKey:@"item"\] retain\];
|
}
1
2
|
Finally, a default handler manages elements that, despite being of interest, are treated uniformly:
|
-defaultElement:children attributes:attributes parser:parser
return [[children lastObject] retain];
1
2
3
4
5
6
7
| }
```
With the parsing routines defined, we move on to initiating the parser. Excluding timing code, the method is fairly simple:
```
\- (void)downloadAndParse:(NSURL \*)url {
|
id pool=[NSAutoreleasePoolnew];
1
| \[parserparse: \[NSDatadataWithContentsOfURL:url\]\];
|
for ( id song in [parserparseResult] ) {
1
| \[selfperformSelectorOnMainThread:@selector(parsedSong:)
|
withObject:song waitUntilDone:NO\];
[pool release];
1
2
3
4
5
6
7
| }
```
Introducing timing code adds complexity:
```
\- (void)downloadAndParse:(NSURL \*)url {
|
id pool=[NSAutoreleasePoolnew];
1
| \[selfperformSelectorOnMainThread:@selector(downloadStarted) withObject:nilwaitUntilDone:NO\];
|
NSData *data=[NSDatadataWithContentsOfURL:url];
1
| \[selfperformSelectorOnMainThread:@selector(downloadEnded) withObject:nilwaitUntilDone:NO\];
|
NSTimeInterval start = [NSDatetimeIntervalSinceReferenceDate];
for ( id song in [parserparseResult] ) {
1
| \[selfperformSelectorOnMainThread:@selector(parsedSong:) withObject:song waitUntilDone:NO\];
|
}
1
| NSTimeInterval duration = \[NSDatetimeIntervalSinceReferenceDate\] - start;
|
[selfperformSelectorOnMainThread:@selector(addToParseDuration:) withObject:[NSNumbernumberWithDouble:duration] waitUntilDone:NO];
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
| \[selfperformSelectorOnMainThread:@selector(parseEnded) withObject:nilwaitUntilDone:NO\];
```
\[\[NSURLCachesharedURLCache\] removeAllCachedResponses\];
\[pool release\];
```
}
```
This creates a non-incremental DOM-style parser: download the data, process it into a DOM, then transfer the objects to the main thread for display. It differs from other DOM parsers by producing domain objects directly, rather than a generic XML DOM requiring further conversion.
Transforming this into a SAX-style parser is surprisingly easy. Instead of returning Song objects from 'itemElement:..', we pass them to the delegate and return 'nil' to prevent tree construction:
```
\[song setReleaseDate:\[parseFormatterdateFromString:\[children objectForUniqueKey:@"releasedate"\]\] \];
```
\[self performSelectorOnMainThread:@selector(parsedSong:)
withObject:song waitUntilDone:NO\];
\[song release\];
return nil;
```
}
```
Consequently, we can eliminate the "channelElement" method and the loop in 'downloadAndParse' responsible for transferring Song objects. While now a SAX-style parser (despite not using SAX methods directly), it remains non-incremental due to parsing occurring only after a complete download.
To create an incremental parser, overlapping processing and downloading, one final change simplifies the 'downloadAndParse' method:
```
\- (void)downloadAndParse:(NSURL \*)url {
|
id pool=[NSAutoreleasePoolnew];
1
| \[parserparseDataFromURL:url\];
|
[[NSURLCachesharedURLCache] removeAllCachedResponses];
}
1
2
3
4
5
6
7
8
9
10
11
|
While optimal for performance, responsiveness, and code size, this doesn't integrate seamlessly with the XMLPerformance example due to the lack of hooks for separating downloading and parsing measurements.
The XMLPerformance example's peculiarity lies in being multi-threaded and measuring real time instead of CPU time for parsing. This poses issues as the scheduler can prioritize the display thread over the parsing thread, incorrectly attributing the time to parsing. This penalizes incremental parsers and explains why Ray's comparison favored DOM parsers.
I hope these explanations provide insights into creating various parser styles using MAX.


|