Objective-C @available保护与更多条件AND运算

26

XCode 9+ / LLVM 5+中的Objective-C具有@available表达式,允许您保护一段代码至少到某个OS版本,以便如果您使用仅在该OS版本上可用的API,则不会发出未受保护的可用性警告。

问题是这种可用性保护仅在if条件中是唯一的表达式时才有效。如果在任何其他上下文中使用它,将会收到警告:

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

例如,如果您尝试将可用性检查与if中的其他条件进行AND运算,则无法正常工作:

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
}

即使可以保证这些代码只能在 iOS 11+ 上运行,但是任何使用 iOS 11 APIs 的代码仍将在 if 块或 some_condition 中生成未经保护的可用性警告。

我可以把它变成两个嵌套的if,但是 else 代码必须重复,这很糟糕(特别是如果代码量很多):

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
}

通过将else块代码重构为匿名函数,我可以避免重复,但这需要在if之前定义else块,这会使代码流程难以理解:

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();
}

有人能提出更好的解决方案吗?


if (@available...)else 块中,难道你也不需要针对 some_condition 进行测试吗? - Nicolas Miari
@NicolasMiari:不是的。 - user102008
我认为你上一个解决方案的变体是最好的,使用方法而不是块,这样方法定义可以在所有条件代码之后。只需用 [self elseMethod]; 替换 elseBlock() 即可。 - RobP
8个回答

14

当您在函数中有复杂条件代码并使流程复杂时,可以像平常一样将其提升到另一个函数中。

- (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
}

你也可以将检查提升到通用代码中(请参见Josh Caswell的代码;它比我最初编写的好)。


7
#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 \
    }

使用方法:

AVAILABLE_GUARD(iOS, 11.0, *, true, {
    printf("IS AVAILABLE");
},
{
    printf("NOT AVAILABLE");
});

它使用@available作为条件,并带有其他可选条件来实现。由于您失去了“守卫”的能力,我抑制了未经保护的警告,但我还添加了额外的保护来保护其余代码。这样,您基本上没有失去任何东西。

您可以得到保护,消除警告并获得额外的条件。


2
把AND操作封装在一个函数里怎么样?
typedef BOOL (^Predicate)();

BOOL elevenAvailableAnd(Predicate predicate)
{
    if (@available(iOS 11.0, *)) {
        return predicate();
    }
    return NO;
}

那么你只有一个分支:

if (elevenAvailableAnd(^{ return someCondition })) {
    // 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
}

如果您愿意,也可以不使用“Block”:
BOOL elevenAvailableAnd(BOOL condition)
{
    if (@available(iOS 11.0, *)) {
        return condition;
    }
    return NO;
}

3
但这样做并不能防止在使用仅适用于iOS 11的API的if块中代码出现未受保护的可用性警告。 - user102008
啊,糟糕。我想“提交一个雷达”可能是正确的答案。不幸的是,这并不能立即解决问题。 - jscs

1
inline bool iOS13()
{
    if(@available(iOS 13, *))
        return true;
    else
        return false;
}

if(iOS13() && x == y)
    //...

2
但这样做将无法防止在使用仅适用于iOS 13的API的if块中的代码(//...)中出现未保护的可用性警告。 - user102008

0
你可以先编写 else 代码并以某种方式存储结果,然后在需要时再编写 if 代码。就像这样:
/**     
 first make default calculations, the 'else-code'
 */
id resultOfCalculations = ... ;

if (@available(iOS 11.0, *)) {
    if (some_condition) {
        /**
         code to run when on iOS 11+ and some_condition is true
         redo calculations and overwrite object
         */
        resultOfCalculations  = ... ;
    }
}

当然,如果条件成立,手机需要进行两次计算,但是不必重复编写代码。

这可能不是最优雅的解决方案,但如果您想保持简单,这是一种替代方法。


0
你也可以简单地使用一个标志:
BOOL doit = FALSE;

if (@available(iOS 11.0, *)) {
  if (some_condition) {
    doit = TRUE;
  }
}

if (doit) {
  // 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
}

3
但这并不能防止在使用仅适用于iOS 11的API的if块中出现未受保护的可用性警告。 - user102008

0
我想到的改变代码布局最少的方法是:
do {
  if (@available(iOS 11.0, *)) {
    if (some_condition) {
      // code to run when on iOS 11+ and some_condition is true
      break;
    }
  }
  // code to run when on older iOS or some_condition is false
} while (0);

这还是很丑的。


我认为在那种情况下你可以使用 goto,这样控制流程跳转会更加清晰明了。 - jscs

0

定义

#define AT_AVAILABLE(...) \
_Pragma("clang diagnostic push") \
_Pragma("clang diagnostic ignored \"-Wunsupported-availability-guard\"") \
_Pragma("clang diagnostic ignored \"-Wunguarded-availability-new\"") \
__builtin_available(__VA_ARGS__) \
_Pragma("clang diagnostic pop")

使用方法:

if (AT_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
}

在 PCH 文件中导入此文件

#pragma clang diagnostic ignored "-Wunsupported-availability-guard"
#pragma clang diagnostic ignored "-Wunguarded-availability-new"

使用方法:

if (AT_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
}


很棒,你提供了一个解决方案。也许你可以添加一些背景信息,比如为什么它比原帖更好?即使这是显而易见的,原则“不要让我思考”仍然适用。 :) - AmitaiB

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接