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:
node1 = [[NSXMLElement elementWithName:@"node1"] retain];
node2 = [[NSXMLElement elementWithName:@"node2"] retain];
NSLog(@"node2: %@", [node2 name]);
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];
NSXMLElement *iqNode = [NSXMLElement elementWithName:@"iq"];
[iqNode addAttributeWithName:@"type" stringValue:@"set"];
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