Objective-C has an @available
expression in XCode 9+ / LLVM 5+ that allows you to guard a block of code to at least a certain OS version so that it won't emit unguarded availability warnings if you use APIs that are only available on that OS version.
The problem is that this availability guarding is that it only works if it's the sole expression in the condition of an if
. If you use it in any other context, you get a warning:
@available does not guard availability here; use if (@available) instead
So for example, it doesn't work if you try to AND the availability check with other conditions in the if
:
if (@available(iOS 11.0, *) && some_condition) { // code to run when on iOS 11+ and some_condition is true } else { // code to run when on older iOS or some_condition is false }
Any code that uses iOS 11 APIs inside the if
block or in some_condition
still will generate unguarded availability warnings, even though it is guaranteed that those pieces of code can only be reached when on iOS 11+.
I could turn it into two nested if
s, but then the else
code would have to be duplicated, which is bad (especially if it's lots of code):
if (@available(iOS 11.0, *)) { if (some_condition) { // code to run when on iOS 11+ and some_condition is true } else { // code to run when on older iOS or some_condition is false } } else { // code to run when on older iOS or some_condition is false }
I can avoid duplication by refactoring the else
block code into an anonymous function, but that requires defining the else
block before the if
, which makes the code flow hard to follow:
void (^elseBlock)(void) = ^{ // code to run when on older iOS or some_condition is false }; if (@available(iOS 11.0, *)) { if (some_condition) { // code to run when on iOS 11+ and some_condition is true } else { elseBlock(); } } else { elseBlock(); }
Can anyone come up with a better solution?
You do what you always do when you have complex conditional code in the middle of a function that makes the flow complex: you hoist it into another function.
- (void)handleThing { if (@available(iOS 11.0, *)) { if (some_condition) { // code to run when on iOS 11+ and some_condition is true return; } } // code to run when on older iOS or some_condition is false }
Or you hoist the check into generic code (see Josh Caswell's; it's better than how I originally wrote this).
#define SUPPRESS_AVAILABILITY_BEGIN \ _Pragma("clang diagnostic push") \ _Pragma("clang diagnostic ignored \"-Wunsupported-availability-guard\"")\ _Pragma("clang diagnostic ignored \"-Wunguarded-availability-new\"") #define SUPPRESS_AVAILABILITY_END \ _Pragma("clang diagnostic pop") #define AVAILABLE_GUARD(platform, os, future, conditions, codeIfAvailable, codeIfUnavailable) \ SUPPRESS_AVAILABILITY_BEGIN \ if (__builtin_available(platform os, future) && conditions) {\ SUPPRESS_AVAILABILITY_END \ if (@available(platform os, future)) { \ codeIfAvailable \ } \ } \ else { \ SUPPRESS_AVAILABILITY_END \ codeIfUnavailable \ }
Usage:
AVAILABLE_GUARD(iOS, 11.0, *, true, { printf("IS AVAILABLE"); }, { printf("NOT AVAILABLE"); });
It works by using @available as a condition with additional optional conditions. Since you lose the ability to "guard", I suppressed the unguarded warnings but I also added an extra guard there to guard the rest of the code.. This makes it so you essentially lost nothing..
You get the guarding, you get the warnings gone and you get the extra conditions..
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With