¿Cuál es la mejor manera de comprobar los arguments del método en Objective-C?

Al codificar un método o function, es una buena práctica verificar los arguments de input para responder a cualquier posible situación de falla.

Por ejemplo:

-(void)insertNameInDictionary:(NSString*)nameString { [myDictionary setObject:nameString forKey:@"Name"]; } 

Esto parece correcto, pero si nameString es nil , la aplicación se bloqueará. Entonces, podemos verificar si es nil , ¿no ?. También podemos verificar si es un NSString y no un NSNumber o si responde a los methods que nuestro método necesita llamar.

Entonces, mi pregunta es: ¿Cuál es la forma más completa y elegante de verificar estos arguments?

Existen múltiples forms de implementar dicha protección:

  • NSParameterAssert
  • __attribute__((nonnull))
  • y una simple testing.

[EDIT] Desde las últimas versiones de Xcode (Xcode 6), Apple agregó annotations de nulabilidad que es otra, y mejor, manera de express si un parámetro puede ser nil / null o no. Debe migrar a esa notación en lugar de utilizar __attribute__((nonnull)) para que sus API sean aún más legibles.


En detalles:

  1. NSParameterAssert es la macro dedicada de Apple que verifica una condición en un parámetro de método y lanza una exception dedicada si falla.

    • Esta es una protección solo en Runtime
    • Esta macro todavía considera que pasar nil como un parámetro es un error de progtwigción / concepción , considerando que debido al flujo de trabajo de su aplicación normalmente no debería ocurrir (debido a otras condiciones que aseguran que el parámetro nunca sea nil por ejemplo) y si no es algo realmente salió mal.
    • Sigue siendo una exception (su código de llamada puede @try/@catch si es necesario), pero tiene la ventaja de que la exception lanzada es más explícita (indicando que el parámetro se esperaba que no fuera nil lugar de estrellarse con un aspecto feo y difícil de entender callstack / post).
  2. Si desea permitir que su código llame a su método con nil pero no haga nada en ese caso, puede simplemente if (!param) return al comienzo de la function / método.

    • Esto considera que pasar nil NO es un error de progtwigción / concepción y, por lo tanto, a veces puede ocurrir debido al flujo de trabajo de su aplicación, por lo que es un caso aceptable que puede suceder y no debe fallar.
  3. Menos conocido es el __attribute__((nonnull)) GCC / LLVM dedicado a decirle al comstackdor que se espera que algunos parameters de una function / método no sean nulos . De esta manera, si el comstackdor puede detectar en time de compilation que intenta llamar a su método / function con un argumento nil / NULL (como llamar directamente a insertNameInDitionary:nil lugar de usar una variable cuyo valor aún no se puede determinar en el momento de compilation) , emitirá un error de compilation de inmediato para que pueda solucionarlo lo antes posible.

  4. [EDIT] Desde la última versión de Xcode 6, puede (y debería) utilizar annotations de __attribute__((nonnull)) lugar de __attribute__((nonnull)) . Vea ejemplos en la publicación de blog de Apple.


Entonces, en resumen:

Si desea marcar que su método ESPERA que su parámetro sea no nil , lo que indica que llamarlo con nil es un error lógico, debe hacer lo siguiente:

 - (void)insertNameInDictionary:(NSString*)nameString __attribute__((nonnull)) { // __attribute__((nonnull)) allows to check obvious cases (directly passing nil) at compile time NSParameterAssert(nameString); // NSParameterAssert allows to check other cases (passing a variable that may or may not be nil) at runtime [myDictionary setObject:nameString forKey:@"Name"]; } 

Si cree que llamar a su método con nil puede suceder y es aceptable , y solo desea evitar un locking en esos casos, simplemente haga cosas como esta:

 -(BOOL)insertNameInDictionary:(NSString*)nameString { if (nameString == nil) return NO; [myDictionary setObject:nameString forKey:@"Name"]; return YES; } 

Y si crees que deberías poder insert un object nil en tu dictionary , puedes convertir el valor nil en NSNull en ese caso específico para que inserte el singleton NSNull dedicado a dicho uso (usé la forma corta de el operador ternario para hacer que el código sea más compacto):

 -(void)insertNameInDictionary:(NSString*)nameString { [myDictionary setObject:nameString?:[NSNull null] forKey:@"Name"]; } 

Y el último caso, si quieres que pase nil en ese ejemplo particular, simplemente elimina el Name de myDictionary , simplemente puedes hacer una simple testing y llamar a removeObjectForKey:@"Name" si nameString es nil y llamar a setObject:forKey: if it's no … o podría usar KVC y el setValue:forKey: genérico setValue:forKey: (método KVC, no específico de NSDictionary en absoluto, por lo que no debe confundirse con setObject:forKey: que en el caso de NSDictionary tiene exactamente el mismo comportamiento (eliminar el key del dictionary si pasamos a nil para el parámetro de valor).

Apple sugiere usar NSAssert para eso:

 NSAssert(nameString, @"nil nameString is not allowed"); 

Esta aserción terminará su progtwig y producirá un post de error que explicará lo sucedido.

Arriba, la parte != nil está implícita, porque Objective-C lo permite. Puede explicarlo explícitamente para una mejor legibilidad:

 NSAssert(nameString != nil, @"nil nameString is not allowed"); 

Las afirmaciones no se limitan a ninguna comprobación, porque pueden tomar condiciones arbitrariamente complejas. Puede verificar que un argumento sea del tipo esperado, que responda a un selector particular, y así sucesivamente. Las aserciones se pueden deshabilitar en el código de versión para save los ciclos de la CPU y la batería.

 - (void)insertNameInDictionary:(NSString *)nameString { if ([nameString isKindOfClass:[NSString class]]) { [myDictionary setObject:nameString forKey:@"Name"]; } } 

Veo un par de problemas aquí. NSMutableDictionary no tiene un método " setObject ". Debe especificar una key.

 -(void)insertNameInDictionary:(NSString*)nameString { if(nameString) [myDictionary setObject:nameString forKey: @"someKey"]; } 

Y también puedes hacer una verificación nula a través del bit " if(nameString) " en mi ejemplo. Si desea tener una cadena vacía, use @"" y no nula.

todo depende de la semántica de lo que quieres que suceda, si quieres eliminar el par KV si el valor es nulo, entonces puedes usar setValue: forKey:

me gusta:

 -(void)insertNameInDictionary:(NSString*)nameString { [myDictionary setValue:nameString forKey:@"Name"]; } 

o si desea almacenar un valor que representa un valor nulo, puede usar NSNull

 -(void)insertNameInDictionary:(NSString*)nameString { [myDictionary setObject:nameString? :[NSNull null] forKey:@"Name"]; } -(NSString *)nameInDictionary { NSString * retVal = [myDictionary objectForKey:@"Name"]; return (retVal==[NSNull null]) ? nil : retVal; } 

o puede que simplemente quiera iniciar session.

 -(void)insertNameInDictionary:(NSString*)nameString { if(!nameString) { NSLog(@"expected nameString to be not null... silly me %s",__PRETTY_FUNCTION__); return; } [myDictionary setObject:nameString forKey:@"Name"]; }