25

Objective-C 在 XCode 9+ / LLVM 5+ 中有一个@available表达式,允许您将代码块保护到至少某个操作系统版本,这样如果您使用仅在该操作系统上可用的 API,它就不会发出未保护的可用性警告操作系统版本。

问题是这种可用性保护只有在它是if. 如果您在任何其他上下文中使用它,您会收到警告:

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

因此,例如,如果您尝试将可用性检查与以下条件中的其他条件相结合,则它不起作用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
}

if任何在块内或仍然使用 iOS 11 API 的代码some_condition都会生成不受保护的可用性警告,即使保证只有在 iOS 11+ 上才能访问这些代码。

我可以把它变成两个嵌套if的 s,但是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我可以通过将块代码重构为匿名函数来避免重复,但这需要在else之前定义块if,这使得代码流难以遵循:

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

任何人都可以提出更好的解决方案吗?

4

8 回答 8

13

当你在一个函数中间有复杂的条件代码使流程变得复杂时,你会做你经常做的事情:你将它提升到另一个函数中。

- (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 的;它比我最初编写的方式更好)。

于 2017-10-26T23:52:37.230 回答
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 作为附加可选条件的条件来工作。由于您失去了“守卫”的能力,我压制了未守卫的警告,但我还在那里添加了一个额外的守卫来守卫代码的其余部分。这使得你基本上什么都没有失去。

你得到了保护,你得到了警告,你得到了额外的条件..

于 2017-10-27T01:00:04.147 回答
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;
}
于 2017-10-26T23:51:47.633 回答
1
inline bool iOS13()
{
    if(@available(iOS 13, *))
        return true;
    else
        return false;
}

if(iOS13() && x == y)
    //...
于 2019-12-11T22:15:06.960 回答
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
}

于 2019-05-10T03:20:14.180 回答
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  = ... ;
    }
}

然后,当然,计算必须通过电话进行两次(如果条件为真),但不必写两次。

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

于 2018-01-25T14:31:20.577 回答
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
}
于 2017-10-26T23:54:32.860 回答
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);

这仍然很丑陋。

于 2017-10-27T18:25:37.483 回答