So with the NSXML class cluster missing from the iPhone, what are our options?
- The NSXMLParser is available. This gives you a SAX parser, and would be helpful if we only needed to read XML. The problem was, we needed to generate a bunch of XML too, so this only gets us halfway there. Plus if we went this route, we'd have to rewrite all the existing code that already uses NSXMLDocument.
- libxml2 is available on the iPhone. This is a C library that's been around for a long time, and it's been available in OS X since like 10.3.9 (or maybe even earlier). This is certainly an option... but it's in C! Surely somebody's made an Objective-C wrapper for it right? Most people think NSXML is a wrapper around libxml. But even if that's true it doesn't help us.
- The Coconut Framework is an Objective-C wrapper around libxml. It's distributed under GPL (but with the possibilty of LGPL or BSD if you talk to the author). It uses a different API than NSXML, likely because it's been around for awhile and NSXML was only added in 10.4. Also, it seems you can only use it to read XML but not generate it.
- TouchXML is another Objective-C wrapper around libxml. It's under a non-restrictive MIT license, and operates as an NSXML replacement. BUT - it gives you read-only support for XML.
- Google's GDataXML classes were the closest I could get. Distributed under the Apache license, they offer an Objective-C wrapper around libxml, and they operate as an NSXML replacement. Plus they offer the ability to read XML, and generate XML with methods such as elementWithName:, addAttribute:, addChild:, etc.
- We present another solution below. Keep reading...
So as a framework developer, with other developers in mind, I had to decide what to do.
If I used Google's GDataXML classes, then I could quickly patch the problem. (I'd only need to define the NSXML classes as GDataXML classes on the iPhone.) But it would offer only a small, small subset of what NSXML offers. Developers using the framework on the iPhone would have to realize this and compensate. Alternatively, I could switch all my code to use the GDataXML classes, but then developers on OS X would reasonably think I'm an idiot.
Also, as I was skimming the source code of all these frameworks I realized something - they are all rather primitive, and don't always operate as NSXML does, and sometimes behave counter-intuitively in an Objective-C world. For example:
- (void)method1
{
node1 = [[NSXMLElement elementWithName:@"node1"] retain];
node2 = [[NSXMLElement elementWithName:@"node2"] retain];
[node1 addChild:node2];
}
- (void)method2
{
NSLog(@"node2: %@", [node2 name]);
[node1 release];
NSLog(@"node2: %@", [node2 name]);
}
Run this code, and you get the name of node2 printed twice. This makes sense from an Objective-C standpoint. (node2 was never released, why would it's name disappear?) But what happens with other frameworks? Try something similar with TouchXML and it doesn't work the same way. Why? Becuase freeing node1 calls xmlNodeFree, which in turn frees all its children including the xmlNodePtr that node2 was wrapping. It works fine with GDataXML - but only because they "cheat" and copy xmlNode subtrees in their addChild: method, which seems a little wasteful considering how most people would use the API. For example:
NSXMLElement *queryNode, *usernameNode, *digestNode, *resourceNode;
queryNode = [NSXMLElement elementWithName:@"query" URI:@"jabber:iq:auth"];
usernameNode = [NSXMLElement elementWithName:@"username" stringValue:username];
digestNode = [NSXMLElement elementWithName:@"digest" stringValue:digest];
resourceNode = [NSXMLElement elementWithName:@"resource" stringValue:resource];
[queryNode addChild:usernameNode];
[queryNode addChild:digestNode];
[queryNode addChild:resourceNode];
NSXMLElement *iqNode = [NSXMLElement elementWithName:@"iq"];
[iqNode addAttributeWithName:@"type" stringValue:@"set"];
[iqNode addChild:queryElement];
In the example above, the GDataXML classes would create the username node once, then copy it so it can be added. The same would happen to the digest and resource nodes. And then the whole queryNode would get copied as well when it's added to the iqNode. Build up a big XML fragment like this, or build up many XML fragments, and there's a lot of wasteful copying going on. And building XML fragments is what our XMPP framework is all about.
There is an inherent danger in using an XML API that acts differently than Apple's. What happens if Apple makes it's NSXML classes public in a future update? We'd probably want to switch the framework to use them - but what if iPhone developers using the framework had already adapted to the alternative XML library. If that library operates differently than NSXML, then the switch could break a lot of code. Plus there's another problem: alot of iPhone developers are also Mac developers. And they would write a single library that would be used both on the desktop and on the phone. And in fact the XMPP framework itself fits into this category.
An optimal solution would be something like this:
- An Objective-C wrapper around libxml
- Ability to read XML
- Ability to generate XML
- Same API as NSXML
- Behaves the same as NSXML
- Behaves like a true Objective-C class
So I decided to write just this. I'm releasing the source code for iPhone developers everwhere in hopes that they never have to directly use libxml themselves, and so that if Apple ever does make their NSXML classes public, the transition can be seamless. I'm calling the framework KissXML, in honor of TouchXML which inspired me to write it. (Sorry Google, I didn't find out about your code until I was already halfway done.)
KissXML Google Code Project Page
7 comments:
A quick question on the XMPP/KissXML combination. It seems that your XMPP implementation uses initWithXMLString in NSXMLElement, however this method is not implemented in your DDXMLElement class. How do you manage to have these two bits working together in a single project? Perhaps the latest SVN versions are just not in sync?
Thanks!
Hi Frank,
The initWithXMLString you're referring to is used in XMPPElement's initWithCoder: method. This initWithCoder method would only be used if one was passing XMPPElements between distributed objects, or archiving it. Since the XMPP framework doesn't directly need any of this stuff, it's not a direct problem for it.
However, it does generate a warning, which is annoying. And I do need DO/archiving abilities for a project I'm working on, so this will be fixed at some point in the future.
Great, thanks! When I get warnings that methods don't exist, I start to think something must be wrong. I'll carry on and ignore it, thanks!
can this be used as a replacement to NSXMLParser? if so how? i have found the NSXMLParser on iphone to leak pretty bad.
Hi Drunknbass,
Yes, the framework can be used as a replacement for the NSXMLParser SAX parser. And it's very, very fast too. In some preliminary benchmarks, it often outperforms the actual NSXML classes for raw parsing of XML.
You should be aware of one limitation though. This isn't a limitation of the specific framework, just a limitation of the DOM model (as opposed to SAX parsing). If you were wanting to parse extremely big files (say 10 MB or more) then your RAM usage with this any DOM framework may be a problem on the iPhone platform.
KissXML is working very well for me, wish I had found it before wasting a weekend on TouchXML (no offense, guys).
The one thing I'm having trouble with is namespaces. The feed I'm reading has a generic namespace (xmlns="http://www.accuweather.com"), and I can't figure out how to configure the DDXML to assign a prefix for this or accept an XPATH without it.
Hi Eric,
Can you post some sample XML, and XPath queries to the KissXML Mailing List?
Post a Comment