Objective-C @доступный охранник AND'ed с большим количеством условий

Objective-C имеет выражение @available в XCode 9+/LLVM 5+, которое позволяет защитить блок кода до по крайней мере, для определенной версии ОС, чтобы она не выдавала предупреждения о неохраняемой доступности, если вы используете 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
}

Любой код, который использует API iOS 11 внутри блока if или в 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();
}

Может ли кто-нибудь придумать лучшее решение?

Ответ 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
}

Или вы можете обойтись без блока, если хотите:

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

Ответ 4

Вы также можете просто использовать флаг:

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
}

Ответ 5

Как я придумал, похоже, что макет кода меньше:

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

который по-прежнему уродлив.

Ответ 6

Вы можете сначала сделать 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  = ... ;
    }
}

Тогда, конечно, вычисление должно быть сделано дважды по телефону (если условия верны), но вам не нужно писать его дважды.

Возможно, это не самое элегантное решение, но если вы хотите, чтобы оно было простым, это альтернатива.

Ответ 7

определенный

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