web-dev-qa-db-fra.com

Gardien Objective-C @available ET avec plus de conditions

Objective-C a une expression @available dans XCode 9+/LLVM 5+ qui vous permet de protéger un bloc de code avec au moins une version du système d'exploitation afin d'éviter l'envoi d'alertes de disponibilité non protégées ne sont disponibles que sur cette version du système d'exploitation.

Le problème est que cette protection de disponibilité est que cela ne fonctionne que si c'est la seule expression dans la condition d'une if. Si vous l'utilisez dans un autre contexte, vous recevez un avertissement:

@available does not guard availability here; use if (@available) instead

Ainsi, par exemple, cela ne fonctionne pas si vous essayez ET de vérifier la disponibilité avec d'autres conditions dans la variable 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
}

Tout code utilisant des API iOS 11 à l'intérieur du bloc if ou de some_condition générera néanmoins des avertissements de disponibilité non protégés, même s'il est garanti que ces éléments de code ne peuvent être atteints que sur iOS 11+.

Je pourrais le transformer en deux ifs imbriquées, mais le code else devrait alors être dupliqué, ce qui est mauvais (surtout si c'est beaucoup de code):

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
}

Je peux éviter la duplication en refacturant le code du bloc else en une fonction anonyme, mais cela nécessite de définir le bloc else avant la if, ce qui rend le flux de code difficile à suivre:

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

Quelqu'un peut-il proposer une meilleure solution?

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

Usage:

AVAILABLE_GUARD(iOS, 11.0, *, true, {
    printf("IS AVAILABLE");
},
{
    printf("NOT AVAILABLE");
});

Cela fonctionne en utilisant @available comme condition avec des conditions optionnelles supplémentaires. Puisque vous perdez la capacité de "garder", j'ai supprimé les avertissements non gardés, mais j'ai également ajouté un garde supplémentaire pour protéger le reste du code. Cela fait que vous n'avez pratiquement rien perdu.

Vous obtenez la garde, vous obtenez les avertissements et les conditions supplémentaires.

7
Brandon

Vous faites ce que vous faites toujours lorsque vous avez un code conditionnel complexe au milieu d'une fonction qui rend le flux complexe: vous le placez dans une autre fonction.

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

Ou alors, vous collez le chèque en code générique (voir celui de Josh Caswell; il vaut mieux que ce que j'ai écrit à l'origine).

6
Rob Napier

Que diriez-vous d'emballer l'AND dans une fonction?

typedef BOOL (^Predicate)();

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

Ensuite, vous n'avez qu'une seule branche:

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
}

Ou vous pouvez vous passer du bloc si vous préférez:

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

Vous pouvez faire le code else en premier et stocker le résultat d’une manière ou d’une autre, puis le if-code si nécessaire. Quelque chose comme ça:

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

Ensuite, bien sûr, le calcul doit être effectué deux fois par téléphone (si les conditions sont vraies), mais vous ne devez pas l'écrire deux fois. 

Peut-être pas la solution la plus élégante, mais si vous voulez garder les choses simples, c'est une alternative.

0
turingtested

La façon dont j'ai proposé cela semble changer le moins possible la disposition du code est la suivante:

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

qui est toujours moche.

0
user102008

Vous pouvez aussi simplement utiliser un drapeau:

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