ios - Objective-C @available guard AND''ed con más condiciones
xcode9 availability (6)
Objective-C tiene una expresión @available
en XCode 9+ / LLVM 5+ que le permite guardar un bloque de código en al menos una determinada versión del sistema operativo para que no emita advertencias de disponibilidad sin vigilancia si utiliza API que solo están disponibles en esa versión del sistema operativo.
El problema es que esta protección de disponibilidad es que solo funciona si es la única expresión en la condición de un if
. Si lo usa en cualquier otro contexto, recibirá una advertencia:
@available does not guard availability here; use if (@available) instead
Por ejemplo, no funciona si intenta Y la disponibilidad verifica con otras condiciones en el 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
}
Cualquier código que use las API de iOS 11 dentro del bloque if
o en some_condition
todavía generará advertencias de disponibilidad sin vigilancia, aunque se garantiza que solo se podrá acceder a esas piezas de código en iOS 11+.
Podría convertirlo en dos anidados if
s, pero luego el código else
tendría que ser duplicado, lo cual es malo (especialmente si es un montón de código):
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
}
Puedo evitar la duplicación refactorizando el código del bloque else
en una función anónima, pero eso requiere definir el bloque else
antes del if
, lo que hace que el flujo del código sea difícil de seguir:
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();
}
¿Alguien puede llegar a una mejor solución?
¿Qué tal si envolvemos el AND en una función?
typedef BOOL (^Predicate)();
BOOL elevenAvailableAnd(Predicate predicate)
{
if (@available(iOS 11.0, *)) {
return predicate();
}
return NO;
}
Entonces solo tienes una rama
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
}
O podrías prescindir del Bloque si prefieres:
BOOL elevenAvailableAnd(BOOL condition)
{
if (@available(iOS 11.0, *)) {
return condition;
}
return NO;
}
La forma en que se me ocurrió que parece cambiar lo menos posible el diseño del código es:
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);
que sigue siendo feo.
También puedes simplemente usar una bandera:
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
}
Usted hace lo que siempre hace cuando tiene un código condicional complejo en medio de una función que hace que el flujo sea complejo: lo eleva a otra función.
- (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
}
O puede colocar el cheque en un código genérico (vea Josh Caswell''s; es mejor que la forma en que escribí esto originalmente).
Usted podría hacer el código else primero y almacenar el resultado de alguna manera, y luego hacer el código if. Algo como esto:
/**
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 variable
*/
resultOfCalculations = ... ;
}
}
Luego, por supuesto, el teléfono debe realizar el cálculo dos veces (si se cumplen las condiciones), pero no tiene que escribirlo dos veces.
Puede que no sea la solución más elegante, pero si solo quiere que sea simple, esta es una alternativa.
#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 /
}
Uso:
AVAILABLE_GUARD(iOS, 11.0, *, true, {
printf("IS AVAILABLE");
},
{
printf("NOT AVAILABLE");
});
Funciona utilizando @available como condición con condiciones opcionales adicionales. Ya que perdiste la habilidad de "proteger", reprimí las advertencias no vigiladas pero también agregué una protección adicional para proteger el resto del código ... Esto hace que no tengas nada en lo esencial.
Obtienes la protección, eliminas las advertencias y obtienes las condiciones adicionales.