web-dev-qa-db-ja.com

Objective-C @利用可能なガードANDより多くの条件で

Objective-Cには、XCode 9+/LLVM 5+で @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
}

ifブロック内またはsome_condition内でiOS 11 APIを使用するコードは、これらのコードがiOS 11以降でのみ到達できることが保証されている場合でも、無防備な可用性警告を生成します。

2つのネストされた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ブロックコードを匿名関数にリファクタリングすることで重複を避けることができますが、それには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();
}

誰もがより良い解決策を思い付くことができますか?

21
user102008

フローを複雑にする関数の途中に複雑な条件コードがある場合は、常に行うことを実行します。それを別の関数に巻き上げます。

- (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の記事を参照してください。最初に書いた方法よりも優れています)。

8
Rob Napier
#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を追加のオプション条件とともに条件として使用することにより機能します。あなたが「ガード」する能力を失うので、私はガードされていない警告を抑制しましたが、コードの残りをガードするためにそこに余分なガードも追加しました。

あなたは警備をし、あなたは警告を失い、あなたは余分な条件を手に入れます。

7
Brandon

定義済み

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

1
Yuxiang

ANDを関数でラップするのはどうですか?

typedef BOOL (^Predicate)();

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

次に、1つのブランチのみがあります。

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
}

または、必要に応じてブロックなしで行うこともできます。

BOOL elevenAvailableAnd(BOOL condition)
{
    if (@available(iOS 11.0, *)) {
        return condition;
    }
    return NO;
}
1
Josh Caswell

最初にelse-codeを実行して結果を何らかの方法で保存し、必要に応じてif-codeを実行できます。このようなもの:

/**     
 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  = ... ;
    }
}

その後、もちろん、計算は電話で2回行われなければなりません(条件が真の場合)。しかし、yoは2回書く必要はありません。

最もエレガントなソリューションではないかもしれませんが、シンプルに保ちたい場合は、これが代替手段です。

0
turingtested

私が思いついた方法は、コードのレイアウトをほとんど変更しないようです:

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

まだいです。

0
user102008

単にフラグを使用することもできます:

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
}
0
Ken Thomases