Swift and Objc interoperability - availability not working only in objc

Viewed 450

While here Apple states that the Swift available flag should be applied also in objc, it's not working for me. What am I doing wrong?


I've got following declarations in Swift files:

@objc protocol Readable: AnyObject {...}

@available(iOS 10.3, *)
@objc class Reader: NSObject, Readable {...}

So let's check if it produces an error when I try to initialize it in pre-ios-10 project without version check. If I write following code in Swift:

let tmp = Reader()

it returns an error:

'Reader' is only available on iOS 10.3 or newer

What is expected.


However if I write following code in objc:

// if (@available(iOS 10.3, *)) { // commeted out to test if it succeeds without version check
    Reader *tmp = [[Reader alloc] init];
// }

The build is finished without any error, while I'd expect the same error as in Swift.


I've tried to mark the class with:

  • @available(iOS 11, *)
  • @available(iOS, introduced: 10.3)

Neither of these works (produces an error) in objc. Any help, please?

1 Answers

Objective-C has had __attribute__((availability)) for longer than it has had @available. To make it work, the Objective-C compiler weakly links symbols that are not available on the deployment target. This means that compiling always succeeds, and starting your app succeeds, but the symbol will be NULL at runtime if it is not available.

Depending on what it is, you'll get more or less graceful degradation when you try to use it:

  • calling a weakly-linked function that is missing is going to crash
  • reading or writing to a global variable that is missing is going to crash
  • using a class that is missing will be a no-op and all methods are going to return zero

The old way of testing whether a symbol is found at runtime is just to compare it to NULL:

NS_AVAILABLE_MAC(...) @interface Foo @end
int bar NS_AVAILABLE_MAC(...);
int baz(int frob) NS_AVAILABLE_MAC(...);

if ([Foo class]) { /* Foo is available */ }
if (&bar) { /* bar is available */ }
if (&baz) { /* baz is available */ }

In your case:

Reader *tmp = [[Reader alloc] init];

tmp will be nil, because that would be the same as [[nil alloc] init].

The @available directive has only relatively recently been added to Objective-C. It's now possible to use @available in Objective-C in the same way that you use #available in Swift. However, to preserve backwards compatibility, it possibly never will be a compile-time error (at default error levels) to try to use a symbol that might not be available on the deployment target in Objective-C.

Related