88

I'm trying to create a universal iPhone app, but it uses a class defined only in a newer version of the SDK. The framework exists on older systems, but a class defined in the framework doesn't.

I know I want to use some kind of weak linking, but any documentation I can find talks about runtime checks for function existence - how do I check that a class exists?

Ben Mosher
  • 13,251
  • 7
  • 69
  • 80
psychotik
  • 38,153
  • 34
  • 100
  • 135

2 Answers2

165

TLDR

Current:

  • Swift: if #available(iOS 9, *)
  • Obj-C, iOS: if (@available(iOS 11.0, *))
  • Obj-C, OS X: if (NSClassFromString(@"UIAlertController"))

Legacy:

  • Swift (versions prior to 2.0): if objc_getClass("UIAlertController")
  • Obj-C, iOS (versions prior to 4.2): if (NSClassFromString(@"UIAlertController"))
  • Obj-C, iOS (versions prior to 11.0): if ([UIAlertController class])

Swift 2+

Although historically it's been recommended to check for capabilities (or class existence) rather than specific OS versions, this doesn't work well in Swift 2.0 because of the introduction of availability checking.

Use this way instead:

if #available(iOS 9, *) {
    // You can use UIStackView here with no errors
    let stackView = UIStackView(...)
} else {
    // Attempting to use UIStackView here will cause a compiler error
    let tableView = UITableView(...)
}

Note: If you instead attempt to use objc_getClass(), you will get the following error:

⛔️ 'UIAlertController' is only available on iOS 8.0 or newer.


Previous versions of Swift

if objc_getClass("UIAlertController") != nil {
    let alert = UIAlertController(...)
} else {
    let alert = UIAlertView(...)
}

Note that objc_getClass() is more reliable than NSClassFromString() or objc_lookUpClass().


Objective-C, iOS 4.2+

if ([SomeClass class]) {
    // class exists
    SomeClass *instance = [[SomeClass alloc] init];
} else {
    // class doesn't exist
}

See code007's answer for more details.


OS X or previous versions of iOS

Class klass = NSClassFromString(@"SomeClass");
if (klass) {
    // class exists
    id instance = [[klass alloc] init];
} else {
    // class doesn't exist
}

Use NSClassFromString(). If it returns nil, the class doesn't exist, otherwise it will return the class object which can be used.

This is the recommended way according to Apple in this document:

[...] Your code would test for the existence of [a] class using NSClassFromString() which will return a valid class object if [the] class exists or nil if it doesnʼt. If the class does exist, your code can use it [...]

Oliver Maksimovic
  • 3,204
  • 3
  • 28
  • 44
Senseful
  • 86,719
  • 67
  • 308
  • 465
  • Thanks. Just to complete the answer for others, once you have detected the class you create it using the `Class` instance returned by `NSClassFromString` (assign it to `id`) and invoke selectors on that instance. – psychotik Jun 17 '10 at 03:12
  • 2
    Notably, this is the _only_ way on OSX, but not the "best" way on iOS (4.2+), though it will work. See code007's answer for iOS specifically. – Ben Mosher Jun 18 '13 at 17:01
  • What if my class is not a real class but rather a C struct? – Nathan H Apr 13 '14 at 08:56
  • Swift 4.1 is coming with new check `canImport`. [Proposal](https://github.com/apple/swift-evolution/blob/master/proposals/0075-import-test.md) – dispatchMain Feb 15 '18 at 04:30
69

For new projects that uses a base SDK of iOS 4.2 or later, there is this new recommended approach which is to use the NSObject class method to check the availability of weakly linked classes at run time. i.e.

if ([UIPrintInteractionController class]) {
    // Create an instance of the class and use it.
} else {
    // Alternate code path to follow when the
    // class is not available.
}

source: https://developer.apple.com/library/content/documentation/DeveloperTools/Conceptual/cross_development/Using/using.html#//apple_ref/doc/uid/20002000-SW3

This mechanism uses the NS_CLASS_AVAILABLE macro, which is available for most framework in iOS (note there may be some framework that do not yet support the NS_CLASS_AVAILABLE - check the iOS release note for this). Extra setting configuration may also be needed that can be read in the Apple's documentation link provided above, however, the advantage of this method is that you get static type checking.

Cœur
  • 37,241
  • 25
  • 195
  • 267
code007
  • 2,306
  • 2
  • 19
  • 26
  • 3
    A little late to the game, but I just ran into this issue when trying to build code that contained `UIAlertController` while still supporting iOS 7. code007's answer is correct, but the extra configuration needed is to weakly link (set from `Required` to `Optional`) UIKit in your project (for this situation, at least). – Evan R Jan 11 '16 at 21:20