Marco.org

I’m : a programmer, writer, podcaster, geek, and coffee enthusiast.

Supporting older versions of iOS while using new APIs

If you wanted your iPhone or iPad app to work with older versions of the OS, or if you wanted to make a universal app that ran on both iPhone and iPad, you need to ensure that the code never tries to call a method or instantiate an object that doesn’t exist on its OS.

For example, the iPhone doesn’t support UIPopoverController, and iOS 4.1 doesn’t support 4.2’s new printing API, so I need to ensure that my universal app never attempts to show a popover on iPhone, and never attempts to print a document on pre-4.2 OSes. If I try to access functionality that doesn’t exist, the app will crash.

The nature of Objective-C requires the linker and runtime to know about any declared types in the app, so even if I have a pointer to an unsupported object, it will crash on launch. The solution before OS 4.2 was to generically type any references with id and use NSClassFromString() to call methods:

if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {

    // dyld errors on iPhones crash the app on launch
    UIPopoverController *p = [[UIPopoverController alloc] init...];

    // dyld won't crash on launch
    id p = [[NSClassFromString(@"UIPopoverController") alloc] init...];
}

Unfortunately, there are some conditions where that couldn’t work, such as if you wanted to have a subclass of a new class in your app. I ran into this issue when making Instapaper 2.3.1’s Print feature, which needed a custom subclass of UIPrintPageRenderer for a feature I wanted. If your app contains a subclass of an unsupported class, even if it’s not instantiated, it will crash on launch. You can make a subclass dynamically, which is like the subclass equivalent of using id and NSClassFromString(), but it’s difficult and messy.

The other option to avoid all of that is weak-linking, which makes the runtime manually look up the existence of every symbol before its first use. Weak-linking allows direct subclassing from new classes and doesn’t require the id pointers and NSClassFromString(). But it can dramatically slow performance if a lot of symbols are weak-linked.

Prior to the iOS 4.2 SDK, you could only mark entire frameworks for weak-linking, which usually meant all of UIKit (and potentially Foundation and libSystem, depending on what you needed). This could cause potentially fatal slowdowns on launch, especially on older hardware, which made this a poor option for many apps.

The iOS 4.2 SDK added a significant improvement: automatic weak-linking of only classes that required it, instead of entire frameworks. (As far as I can tell, it’s an automatic and officially supported version of this workaround.) But it only enables this support under certain conditions:

If you build under these conditions, you can safely subclass or have pointers to whatever you want (as long as you’re careful not to instantiate them when they’re not available), and you can check for a class’ availability at runtime like this:

if ([UIPopoverController class] != nil) {
    // UIPopoverController is available, use it
    UIPopoverController *p = [[UIPopoverController alloc] init...];
}

And your app can be compatible with all iPads, and all iPhones and iPods Touch back to version 3.1.


  1. This is actually a bug that’s fixed for the upcoming LLVM compiler 1.7. ↩︎