it-swarm.com.ru

Objective-C @available Guard AND с большим количеством условий

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+.

Я мог бы превратить его в две вложенные 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();
}

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

19
user102008
#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

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

- (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's; это лучше, чем то, как я изначально написал это).

6
Rob Napier

Как насчет упаковки 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;
}
2
Josh Caswell

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

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

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

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