[Citation]NSXML on the iPhone

2 Comments

We recently ran into a huge roadblock. We had created an XMPP framework in Cocoa, and we were hoping it would easily port to the iPhone. Turns out we were wrong, because Apple decided to make the NSXML classes (NSXMLDocument, NSXMLElement, NSXMLNode, etc) private! They are supposedly in the private Office framework, however private frameworks can’t be used for any intensive purposes. So there we were, heavily invested in NSXML, and Apple pulls the floor out from under our feet. Some web searching quickly revealed that we weren’t the only ones in this particular situation. It would seem that Apple is more interested in supporting gaming these days…

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

[Citation]Developing iPhone Apps with 4.x SDK, Deploying to 3.x Devices : Base SDK and iPhone OS Deployment Target

Leave a comment

Posted on July 6, 2010 by John Muchow in Xcode

If you’ve installed Xcode 3.2.3 you quickly became aware that the only SDK’s packaged with this version are 4.0 and 3.2. Until 4.x has widespread adoption, chances are you’ll want your applications to run on earlier versions of the iPhone OS (iOS) SDK. You can accomplish this feat through two configuration options within Xcode, the Base SDK and the iPhone OS Deployment Target.

Base SDK

The Base SDK is the version of SDK that will be used when compiling your application – the compiler will use the headers and libraries of this specific SDK. For example, in the image below notice that there are only two choices for the Base SDK for both the device and simulator, versions 3.2 and 4.0.

However, we have an issue here, if you build an application with the latest SDK and deploy on a device with an earlier OS, chances are your application will crash if you reference any code in a 4.x API while running the application on a 3.x device. Also, the 3.2 SDK is for the iPad only, so this isn’t an option when deploying to an iPhone or iPod touch.

iPhone OS Deployment Target

To specify which OS version is the minimum that your application will support, you set the deployment target. Your application will then run on this minimum OS as well as all later versions.

This is all well and good, however, the obvious question is how to deploy on an earlier OS version yet take advantage of features for those devices that are running a later OS?

Check for Feature Availability Not OS Version

When targeting an earlier OS as mentioned above, yet you want to take advantage of features of a later OS for devices that support it, you can use the method respondsToSelector to check if the receiver implements or inherits a method that can respond to a specified message. This approach follows Apple’s recommendation to check for availability of features versus a specific OS version.

In the example below the code will check if the object returned by [UIDevice currentDevice] will respond to the selector shown, if so, you can write relevant multi-tasking code:

if ([[UIDevice currentDevice] respondsToSelector:@selector(isMultitaskingSupported)])
{
  // Multi-tasking code for supported devices
}
else
{
  // Devices without multi-tasking support
}

Another time when that you may choose to use respondsToSelector is for cases when the implementation of the same API has changed with a later OS version. I ran into this particular case when writing code for displaying movies using the MPMoviePlayerController. In the 4.x SDK the notifications have changed slightly – in 2.0 to 3.1 MPMoviePlayerContentPreloadDidFinishNotification was the notification you would use to receive notice that a movie was ready to play. With 3.2 (iPad) and later, MPMoviePlayerContentPreloadDidFinishNotification has been deprecated and MPMoviePlayerLoadStateDidChangeNotification has taken its place.

The code example below shows how you may go about using respondsToSelector to figure out which notification you should request based on the whether or not the movie player can return information about its load state:

MPMoviePlayerController *mp =
   [[MPMoviePlayerController alloc] initWithContentURL:movieURL];

// This method is available on 3.2 and greater...
 if ([mp respondsToSelector:@selector(loadState)])
{
  // Register to receive notification when load state changed
  // (check for playable, stalled...)
  [[NSNotificationCenter defaultCenter] addObserver:self
                       selector:@selector(moviePlayerLoadStateChanged:)
                       name:MPMoviePlayerLoadStateDidChangeNotification
                       object:nil];

}
else
{
  // Register to receive a notification when the movie is ready to play.
  [[NSNotificationCenter defaultCenter] addObserver:self
                         selector:@selector(moviePreloadDidFinish:)
                         name:MPMoviePlayerContentPreloadDidFinishNotification
                         object:nil];

}
Check for Function Availability

Beyond working with objects, if you need to check for the availability of a specific function, you do so by comparing the function to NULL. In the example below I check for a function related to creating a PDF-based graphics context (available in 3.2 and later).

if (UIGraphicsBeginPDFContextToFile != NULL)
{
  // The function is available
}
Testing Across OS Versions

Unfortunately, with the upgrade to the latest Xcode (3.2.3) there are no simulators for 3.1.x and earlier OS versions. Although it has always been recommended to test your apps on device, at this point it becomes a necessity.