Checking API Availability with Swift

Swift 2 has some improvements that make it easier and safer to check if an API is available to use for a targeted iOS version.

Recap of Objective-C Approach

Before looking at Swift let’s take a brief recap of how we have been checking for SDK availability with Objective-C.

Checking for class/framework availability

The release of iOS 9, like all major releases, introduced many new frameworks. If your deployment target is older than iOS 9 you need to weak link any new framework you use and then check the availability of the class at run time. For example, if we want to use the new Contacts framework on iOS 9 but be able to fall back to the older address book framework on iOS 8:

if ([CNContactStore class]) {
  CNContactStore *store = [CNContactStore new];
  //...
} else {
  // Fallback to old framework
}

Check for method availability

Use respondsToSelector to check for a method added to a framework. For example, iOS 9 introduced the allowsBackgroundLocationUpdates property to Core Location:

CLLocationManager *manager = [CLLocationManager new];
if ([manager respondsToSelector:@selector(setAllowsBackgroundLocationUpdates:)]) {
  // Not available in iOS 8
  manager.allowsBackgroundLocationUpdates = YES;
}

Pitfalls

These methods are painful to maintain and not as safe as they seem. Consider what happens if we test for the availability of a now public symbol that in earlier releases was private to Apple. For example, there are several new text styles in iOS 9 including UIFontTextStyleCallout. To only use this style from iOS 9 you could try to check for the symbol, expecting it to be null on iOS 8:

if (&UIFontTextStyleCallout != nil) {
  textLabel.font = [UIFont preferredFontForTextStyle:UIFontTextStyleCallout];
}

Unfortunately this does not work as expected. It turns out the symbol did exist in iOS 8 it just was not publicly declared. Using a private method or value risks unexpected results and is not what we want to be doing.

Swift 2 Approach

With Swift the availability checks are now built-in and checked at compile time. This means Xcode can tell us when we use an API that is unavailable for our deployment target. For example if I try to use CNContactStore with an iOS 8 deployment target Xcode suggests the fix below:

if #available(iOS 9.0, *) {
  let store = CNContactStore()
} else {
  // Fallback on earlier versions
}

You can use the same approach for methods where before we used respondsToSelector:

let manager = CLLocationManager()
if #available(iOS 9.0, *) {
  manager.allowsBackgroundLocationUpdates = true
}

Availability Condition

The #available condition takes a list of platforms (ios, OSX, watchOS) and versions. For example, for code that should only run on iOS 9 or OS X 10.10:

if #available(iOS 9, OSX 10.10, *) {
  // Code to execute on iOS 9, OS X 10.10
}

You always need the final * wildcard to cover the other unspecified platforms even if your App is not targeting them.

You can improve readability and use #available with a guard statement for early return from a function if you have a block of code that is conditional:

private func somethingNew() {
  guard #available(iOS 9, *) else { return }

  // do something on iOS 9
  let store = CNContactStore()
  let predicate = CNContact.predicateForContactsMatchingName("Zakroff")
  let keys = [CNContactGivenNameKey, CNContactFamilyNameKey]
  ...
}

If you have a whole function or class that should be conditionally available use @available:

@available(iOS 9.0, *)
private func checkContact() {
  let store = CNContactStore()
  // ...
}

Compile Time Safety

To finish up, let’s look again at the problem with a symbol public in iOS 9 but private in iOS 8. If we try to set the preferred font to an iOS 9 only style we now get a compile time error for iOS 8 targets:

label.font = UIFont.preferredFontForTextStyle(UIFontTextStyleCallout)
> 'UIFontTextStyleCallout' is only available on iOS 9.0 or newer

Swift makes it easy to test and fallback to a sensible default depending on the platform version:

if #available(iOS 9.0, *) {
  label.font = UIFont.preferredFontForTextStyle(UIFontTextStyleCallout)
} else {
  label.font = UIFont.preferredFontForTextStyle(UIFontTextStyleBody)
}

Further Reading

Never miss a post!

iOS Size Classes Cheat Sheet

Subscribe and get my free iOS Size Classes Cheat Sheet

Unsubscribe at any time.
No time to watch WWDC videos?

Sign up to get my iOS posts direct to your inbox and I will send you a free PDF of my iOS Size Classes Cheat Sheet.

Unsubscribe at any time.
Archives Categories
comments powered by Disqus