Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Negating Objective-C's @available keyword

I would like to run a piece of code only if the iOS version of the current device is below a specific version, as specified here. The code examples given by Apple look like this:

if (@available(iOS 10.0, *)) {
  // iOS 10.0 and above
} else {
  // below 10.0
}

However, there are scenarios where one would like to run code only if the current iOS version is below a specific version. I assumed the following code will work:

if (!@available(iOS 10.0, *)) {
  // below 10.0
}

However it seems that this doesn't work, and I'm getting the following warning from Xcode:

@available does not guard availability here; use if (@available) instead

Here is the LLVM commit that added the diagnostic I'm seeing.

There are two possible fallbacks to that issue:

  1. Use the if-else variant without adding any code to the if block (not very elegant).
  2. Continue to use old approaches such as -[NSProcessInfo isOperatingSystemAtLeastVersion:].

Is there another intended way to use @available that I'm missing?

like image 478
StatusReport Avatar asked Sep 24 '17 09:09

StatusReport


2 Answers

The idea of @available is that you want to use API that is only available on certain systems. For other systems, the functionality is either missing in your app or you offer alternative functionality. The correct way to use it for cases where you don't need any code beyond a certain OS version, only below, is

if (@available(iOS 10.0, *)) {
    // Happens automatically on on iOS 10 and beyond
} else {
    someOtherCode();
}

The reason for that is that the compiler sometimes has to perform some extra magic to code guarded by @available and therefore it needs to clearly recognize when this is the case. So in fact it explicitly searches for

if (@available(...)) {

with only variations in spaces and line breaks allowed. Yes, you may ague that a single not (!) is really anything but complicated yet where would you then draw the line? What about:

if ((todayIsTuesday() && @available(iOS 9.0, *)) 
    || (self.theWeatherIsNice && !@available(iOS 11.0, *)) {

Thus only a simple statements are allowed that only force the compiler to divide code into two sections and where always only one section will run for sure: One for the listed OSes and one for the rest. Of course, "the rest" can then be subdivided again, else if is allowed. Only the else section can be auto-generated if missing, so when you write this:

if (@available(...)) {
    someCode();
} // There is no else

The compiler is also happy as that is the same as

if (@available(...)) {
     someCode();
} else {
    // Nothing to do here
}
like image 141
Mecki Avatar answered Nov 08 '22 06:11

Mecki


You can define your own custom macros that you can use throughout your application. Example:-

#define isIOS11() ([[UIDevice currentDevice].systemVersion doubleValue]>= 11.0 && [[UIDevice currentDevice].systemVersion doubleValue] < 12.0)

or

#define SinceIOS9_2 ([[UIDevice currentDevice].systemVersion doubleValue]>= 4.2 && [[UIDevice currentDevice].systemVersion doubleValue] < 9.2)

Use it like below:-

if (isIOS11()) {
    // Do something for iOS 11 
} else {
    // Do something iOS Versions below 11.0
}

Please let me know if this works for you.

like image 44
Jitendra Avatar answered Nov 08 '22 05:11

Jitendra