Solving Backwards Compatibility Issues
Once you have identified the backwards compatibility issues in your app, the next step is to figure out how to fix them. Each new version of iOS introduces new frameworks, classes, methods, constants and enumeration values — and there’s a specific strategy to deal with each of these.Unsupported frameworks
Linking against a framework that doesn’t exist in your deployment target will just cause the dynamic linker to crash your app. To solve this, you mark unsupported frameworks as “Optional” in your project settings.
To do this, select your project under the “Targets” section, and open up Build Phases -> Link Binary With Libraries. Next to each framework you can specify either Required or Optional. Selecting Optional will weak link the framework and solve your compatibility issue.
Note: You can read more about weak linking in Apple’s Framework Programming Guide.
Unsupported classesSometimes you want to use a class that exists in your base SDK, but not in your deployment target. To do this you need to check the availability of this class at runtime to avoid crashing your app. It crashes because this is what the Objetive-C runtime will do if you try to use a class that doesn’t exist. As of iOS 4.2, classes are weakly linked so you can use the
+class
method to perform the runtime check. For example:if ([SLComposeViewController class]) { //Safe to use SLComposeViewController } else { //Fail gracefully } |
Similarly, if you’re using a method in your base SDK that doesn’t exist in your deployment target, you can avoid nasty crashes by using a little introspection.
The methods
-respondsToSelector:
and +instancesRespondToSelector:
will both do the trick, as shown in the code examples below:if ([self.image respondsToSelector:@selector(resizableImageWithCapInsets:resizingMode:)]) { //Safe to use this way of creating resizable images } else { //Fail gracefully } |
respondsToSelector:
on the class itself, like so:if ([UIView respondsToSelector:@selector(requiresConstraintBasedLayout)]) { //Safe to use this method } else { //Fail gracefully } |
Note: If you want to check for the presence of a certain property on a class, then you can do so by testing that instances respond to the property getter or setter.
For example, to check if
Unsupported constants/C functionsFor example, to check if
UILabel
can take an attributed string via its attributedText
property (new in iOS 6), perform introspection against the implicit setter method @selector(setAttributedText)
.Sometimes a constant value is the missing piece of the deployment target; it usually takes the form of an
extern NSString *
or a C function. In this case, you can perform a runtime check against NULL to determine if it exists.For example, the C function
ABAddressBookCreateWithOptions(...)
was introduced in iOS 6 but can still live safely in your iOS 5 app like so:if (ABAddressBookCreateWithOptions != NULL) { //Safe to use } else { //Fail gracefully } |
UIApplicationWillEnterForegroundNotification
, you would simply validate it as so:if (&UIApplicationWillEnterForegroundNotification) { //Safe to assume multitasking support } else { //Fail gracefully } |
UIApplicationWillEnterForegroundNotification
is simply an extern NSString *
declared at the bottom of the document. When your application is loaded into memory, these strings are initialized and stay resident in memory. The
&
operator gets the string’s memory address. If the memory address is not equal to nil
, then the NSString
is available, otherwise it’s not available and your code will have to work around that fact.
Note: The mechanism that makes this work is weak linking, which was described earlier. When a binary is loaded the dynamic linker replaces in the app binary any addresses of things (functions, constants, etc) in dynamically loaded libraries. If it’s weakly linked then if the symbol in the library is not found then the address is set to NULL.
Unsupported enumeration valuesChecking for the existence of enumeration or bit mask values — the kind that you would find inside an
NS_ENUM
or NS_OPTIONS
declaration — is incredibly difficult to check at runtime. Why? Under the hood, an enumeration is just a method of giving names to numeric constants. An enum is replaced with an
int
when compiled, which always exists!If you’re faced with the situation of needing to see if a certain enumeration exists, all you can do is either explicitly check the OS version (which isn’t recommended) or alternatively check against another API element that was introduced at the same time as the new enumeration value.
Note: Whichever way you do this, be sure to add adequate comments to any such code and consider wrapping the code in a dedicated compatibility helper.
Explicit iOS version checkingYou should generally stay away from checking explicit iOS versions but there are specific situations where it’s unavoidable. For example, if you need to account for a bugfix in a previously available method, use the following line of code to return the OS version:
NSString *osVersion = [[UIDevice currentDevice] systemVersion]; |
compare:options:
method, passing NSNumericSearch
as the options to compare OS versions. This takes into account the fact that 6.1.1 is greater than 6.1.0. If you converted them to floats first and then used standard arithmetic things would go wrong because both would parse as the number 6.1.
Note: You can find out more about this topic in Apple’s SDK Compatibility Guide.