【问题标题】:Objective-C @available guard AND'ed with more conditionsObjective-C @available 守卫与更多条件
【发布时间】:2018-04-08 11:41:18
【问题描述】:

Objective-C 在 XCode 9+ / LLVM 5+ 中有一个 @available expression,它允许您将代码块保护到至少某个操作系统版本,这样如果您使用的 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 块或 some_condition 中使用 iOS 11 API 的代码仍会生成无人看管的可用性警告,即使保证只有在 iOS 11+ 上才能访问这些代码。

我可以把它变成两个嵌套的ifs,但是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 进行测试 ...?
  • @NicolasMiari:不
  • 我认为您最后一个解决方案的变体是最好的,使用方法而不是块,以便方法定义可以在所有这些条件代码之后。只需将elseBlock() 替换为[self elseMethod];

标签: ios objective-c xcode9 availability


【解决方案1】:

当您在函数中间有复杂的条件代码使流程变得复杂时,您会像往常一样做:将其提升到另一个函数中。

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

【讨论】:

    【解决方案2】:
    #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 作为附加可选条件的条件来工作。由于你失去了“守卫”的能力,我压制了无人看守的警告,但我还在那里添加了一个额外的守卫来保护其余的代码。这使得你基本上什么都没有失去。

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

    【讨论】:

      【解决方案3】:

      如何将 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;
      }
      

      【讨论】:

      • 但它不会阻止使用 iOS 11-only API 的 if 块中的代码中出现无人看管的可用性警告。
      • 啊。废话。我想“归档雷达”可能是正确的答案。不幸的是,这在短期内没有帮助。
      【解决方案4】:
      inline bool iOS13()
      {
          if(@available(iOS 13, *))
              return true;
          else
              return false;
      }
      
      if(iOS13() && x == y)
          //...
      

      【讨论】:

      • 但它不会阻止使用 iOS 13-only API 的 if 块(//...)中的代码中出现无人看管的可用性警告。
      【解决方案5】:

      你也可以简单地使用一个标志:

      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
      }
      

      【讨论】:

      • 但它不会阻止 if 块中使用仅 iOS 11 API 的代码中出现无人看管的可用性警告。
      【解决方案6】:

      我想出的最不改变代码布局的方法是:

      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;控制是如何跳跃的会更清楚。
      【解决方案7】:

      您可以先执行 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  = ... ;
          }
      }
      

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

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

      【讨论】:

        【解决方案8】:

        定义

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

        【讨论】:

        • 很高兴您提供了解决方案。也许您可以添加一些上下文,例如为什么它比原始海报更好?即使很明显,“不要让我思考”的原则仍然应该适用。 :)
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2013-03-31
        • 2011-12-22
        • 2023-03-04
        • 1970-01-01
        • 2012-01-19
        相关资源
        最近更新 更多