Цель-C @доступная защита И дополнительные условия

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, но тогда код 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();
}

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


person user102008    schedule 26.10.2017    source источник
comment
Разве вам не нужно также тестировать some_condition также в блоке else блока if (@available... ...?   -  person Nicolas Miari    schedule 27.10.2017
comment
@NicolasMiari: нет   -  person user102008    schedule 27.10.2017
comment
Я думаю, что вариант вашего последнего решения является лучшим, используя метод вместо блока, чтобы определение метода могло быть ПОСЛЕ всего этого условного кода. Просто замените elseBlock() на [self elseMethod];   -  person RobP    schedule 27.06.2018


Ответы (8)


Вы делаете то, что всегда делаете, когда у вас есть сложный условный код в середине функции, который усложняет поток: вы поднимаете его в другую функцию.

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

Или вы вставляете чек в общий код (см. код Джоша Касуэлла; это лучше, чем то, как я изначально написал это).

person Rob Napier    schedule 26.10.2017

#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 в качестве условия с дополнительными необязательными условиями. Поскольку вы теряете способность «защищать», я подавил незащищенные предупреждения, но также добавил дополнительную защиту для защиты остальной части кода. Это делает так, что вы, по сути, ничего не потеряли.

Вы получаете охрану, вы убираете предупреждения и получаете дополнительные условия.

person Brandon    schedule 27.10.2017

Как насчет включения И в функцию?

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;
}
person jscs    schedule 26.10.2017
comment
Но тогда это не предотвратило бы незащищенные предупреждения о доступности в коде в блоке if, который использует API только для iOS 11. - person user102008; 27.10.2017
comment
Ах. Дерьмо. Я предполагаю, что подать радар, вероятно, будет правильным ответом. К сожалению, это не поможет в ближайшее время. - person jscs; 27.10.2017

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

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
}
person Ken Thomases    schedule 26.10.2017
comment
Но тогда это не предотвратило бы незащищенные предупреждения о доступности в коде в блоке if, который использует API только для iOS 11. - person user102008; 27.10.2017

То, как я придумал, похоже, меньше всего меняет макет кода:

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

что все равно некрасиво.

person user102008    schedule 27.10.2017
comment
Я думаю, вы также можете использовать goto в этот момент; было бы намного яснее, как прыгал контроль. - person jscs; 28.10.2017

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

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

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

person turingtested    schedule 25.01.2018

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

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

or

импортировать это в файл 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
}

person Yuxiang    schedule 10.05.2019
comment
Очень хорошо, что вы предложили решение. Возможно, вы можете добавить некоторый контекст, например, почему это лучше, чем то, что было на оригинальном плакате? Даже если это очевидно, принцип «Не заставляй меня думать» все равно должен применяться. :) - person AmitaiB; 10.05.2019

person    schedule
comment
Но тогда это не предотвратило бы незащищенные предупреждения о доступности в коде в блоке if (//...), который использует API только для iOS 13. - person user102008; 12.12.2019