¿Cuál es la mejor manera de lanzar una excepción en Objective-C / Cocoa?
¿Cuál es la mejor manera de lanzar una excepción en Objective-C / Cocoa?
Respuestas:
Yo uso de la [NSException raise:format:]
siguiente manera:
[NSException raise:@"Invalid foo value" format:@"foo of %d is invalid", foo];
Una palabra de precaución aquí. En Objective-C, a diferencia de muchos lenguajes similares, generalmente debe tratar de evitar el uso de excepciones para situaciones de error comunes que pueden ocurrir en la operación normal.
La documentación de Apple para Obj-C 2.0 establece lo siguiente: "Importante: las excepciones son intensivas en recursos en Objective-C. No debe utilizar excepciones para el control de flujo general, o simplemente para indicar errores (como un archivo no accesible)"
La documentación conceptual de manejo de excepciones de Apple explica lo mismo, pero con más palabras: "Importante: debe reservar el uso de excepciones para la programación o errores inesperados de tiempo de ejecución tales como acceso a colecciones fuera de límites, intentos de mutar objetos inmutables, enviar un mensaje no válido y perder la conexión con el servidor de Windows. Por lo general, se ocupa de este tipo de errores con excepciones cuando se crea una aplicación en lugar de en tiempo de ejecución. [.....] En lugar de excepciones, objetos de error (NSError) y El mecanismo de entrega de errores de cacao es la forma recomendada de comunicar los errores esperados en las aplicaciones de cacao ".
Las razones para esto es en parte para adherirse a los modismos de programación en Objective-C (usando valores de retorno en casos simples y parámetros de referencia (a menudo la clase NSError) en casos más complejos), en parte que lanzar y atrapar excepciones es mucho más costoso y Finalmente (y lo más importante es que) las excepciones de Objective-C son un envoltorio delgado alrededor de las funciones setjmp () y longjmp () de C, que esencialmente arruinan su manejo cuidadoso de la memoria, vea esta explicación .
@throw([NSException exceptionWith…])
Xcode reconoce las @throw
declaraciones como puntos de salida de funciones, como las return
declaraciones. El uso de la @throw
sintaxis evita las advertencias erróneas de " Control puede llegar al final de la función no nula " que puede obtener [NSException raise:…]
.
Además, @throw
se puede usar para lanzar objetos que no son de la clase NSException.
En cuanto a [NSException raise:format:]
. Para aquellos que provienen de un fondo de Java, recordarán que Java distingue entre Exception y RuntimeException. La excepción es una excepción marcada y RuntimeException no está marcada. En particular, Java sugiere usar excepciones marcadas para "condiciones de error normales" y excepciones no verificadas para "errores de tiempo de ejecución causados por un error del programador". Parece que las excepciones de Objective-C deberían usarse en los mismos lugares donde usaría una excepción no verificada, y los valores de retorno del código de error o los valores de NSError se prefieren en los lugares donde usaría una excepción marcada.
Creo que para ser consistente es mejor usar @throw con su propia clase que extiende NSException. Luego usas las mismas anotaciones para intentar atrapar finalmente:
@try {
.....
}
@catch{
...
}
@finally{
...
}
Apple explica aquí cómo lanzar y manejar excepciones: Excepciones de captura Excepciones de lanzamiento
Desde ObjC 2.0, las excepciones de Objective-C ya no son un contenedor para setjmp () longjmp () y son compatibles con la excepción de C ++, @try es "gratis", pero lanzar y capturar excepciones es mucho más costoso.
De todos modos, las aserciones (usando la familia de macros NSAssert y NSCAssert) arrojan NSException, y es sensato usarlas como estados Ries.
Use NSError para comunicar fallas en lugar de excepciones.
Puntos rápidos sobre NSError:
NSError permite que los códigos de error de estilo C (enteros) identifiquen claramente la causa raíz y, con suerte, permitan que el controlador de errores supere el error. Puede envolver códigos de error de bibliotecas C como SQLite en instancias de NSError muy fácilmente.
NSError también tiene la ventaja de ser un objeto y ofrece una forma de describir el error con más detalle con su miembro de diccionario userInfo.
Pero lo mejor de todo es que NSError NO PUEDE lanzarse, por lo que fomenta un enfoque más proactivo para el manejo de errores, en contraste con otros lenguajes que simplemente arrojan la papa caliente más arriba y más arriba en la pila de llamadas, en cuyo punto solo se puede informar al usuario y no se maneja de manera significativa (no si cree en seguir el principio más grande de ocultación de información de OOP que es).
Enlace de referencia : Referencia
Creo que nunca debe usar Excepciones para controlar el flujo normal del programa. Pero se deben lanzar excepciones siempre que algún valor no coincida con el valor deseado.
Por ejemplo, si alguna función acepta un valor, y ese valor nunca se permite que sea nulo, entonces está bien lanzar una excepción en lugar de intentar hacer algo 'inteligente' ...
Ries
Solo debe lanzar excepciones si se encuentra en una situación que indica un error de programación y desea detener la ejecución de la aplicación. Por lo tanto, la mejor manera de generar excepciones es usar las macros NSAssert y NSParameterAssert, y asegurarse de que NS_BLOCK_ASSERTIONS no esté definido.
Código de muestra para el caso: @throw ([NSException exceptionWithName: ...
- (void)parseError:(NSError *)error
completionBlock:(void (^)(NSString *error))completionBlock {
NSString *resultString = [NSString new];
@try {
NSData *errorData = [NSData dataWithData:error.userInfo[@"SomeKeyForData"]];
if(!errorData.bytes) {
@throw([NSException exceptionWithName:@"<Set Yours exc. name: > Test Exc" reason:@"<Describe reason: > Doesn't contain data" userInfo:nil]);
}
NSDictionary *dictFromData = [NSJSONSerialization JSONObjectWithData:errorData
options:NSJSONReadingAllowFragments
error:&error];
resultString = dictFromData[@"someKey"];
...
} @catch (NSException *exception) {
NSLog( @"Caught Exception Name: %@", exception.name);
NSLog( @"Caught Exception Reason: %@", exception.reason );
resultString = exception.reason;
} @finally {
completionBlock(resultString);
}
}
Utilizando:
[self parseError:error completionBlock:^(NSString *error) {
NSLog(@"%@", error);
}];
Otro caso de uso más avanzado:
- (void)parseError:(NSError *)error completionBlock:(void (^)(NSString *error))completionBlock {
NSString *resultString = [NSString new];
NSException* customNilException = [NSException exceptionWithName:@"NilException"
reason:@"object is nil"
userInfo:nil];
NSException* customNotNumberException = [NSException exceptionWithName:@"NotNumberException"
reason:@"object is not a NSNumber"
userInfo:nil];
@try {
NSData *errorData = [NSData dataWithData:error.userInfo[@"SomeKeyForData"]];
if(!errorData.bytes) {
@throw([NSException exceptionWithName:@"<Set Yours exc. name: > Test Exc" reason:@"<Describe reason: > Doesn't contain data" userInfo:nil]);
}
NSDictionary *dictFromData = [NSJSONSerialization JSONObjectWithData:errorData
options:NSJSONReadingAllowFragments
error:&error];
NSArray * array = dictFromData[@"someArrayKey"];
for (NSInteger i=0; i < array.count; i++) {
id resultString = array[i];
if (![resultString isKindOfClass:NSNumber.class]) {
[customNotNumberException raise]; // <====== HERE is just the same as: @throw customNotNumberException;
break;
} else if (!resultString){
@throw customNilException; // <======
break;
}
}
} @catch (SomeCustomException * sce) {
// most specific type
// handle exception ce
//...
} @catch (CustomException * ce) {
// most specific type
// handle exception ce
//...
} @catch (NSException *exception) {
// less specific type
// do whatever recovery is necessary at his level
//...
// rethrow the exception so it's handled at a higher level
@throw (SomeCustomException * customException);
} @finally {
// perform tasks necessary whether exception occurred or not
}
}
No hay ninguna razón para no usar excepciones normalmente en el objetivo C, incluso para significar excepciones de reglas comerciales. Apple puede decir usar NSError a quién le importa. Obj C ha existido durante mucho tiempo y, en un momento, TODA la documentación de C ++ decía lo mismo. La razón por la que no importa cuán costoso es lanzar y atrapar una excepción, es que la vida útil de una excepción es extremadamente corta y ... es una EXCEPCIÓN al flujo normal. Nunca he oído a nadie decir en mi vida, hombre, esa excepción tardó mucho tiempo en ser arrojada y atrapada.
Además, hay personas que piensan que el objetivo C en sí es demasiado costoso y en su lugar codifica en C o C ++. Por lo tanto, decir que siempre use NSError está mal informado y es paranoico.
Pero la pregunta de este hilo aún no ha sido respondida, ¿cuál es la MEJOR forma de lanzar una excepción? Las formas de devolver NSError son obvias.
Entonces, ¿es: [NSException raise: ... @throw [[NSException alloc] initWithName .... o @throw [[MyCustomException ...?
Utilizo la regla marcada / no marcada aquí ligeramente diferente que la anterior.
La diferencia real entre (usando la metáfora de Java aquí) marcada / desmarcada es importante -> si puede recuperarse de la excepción. Y por recuperación me refiero no solo a NO chocar.
Por lo tanto, utilizo clases de excepción personalizadas con @throw para excepciones recuperables, porque es probable que tenga algún método de aplicación que busque ciertos tipos de fallas en múltiples bloques @catch. Por ejemplo, si mi aplicación es un cajero automático, tendría un bloque @catch para "WithdrawalRequestExceedsBalanceException".
Uso NSException: raise para excepciones de tiempo de ejecución ya que no tengo forma de recuperarme de la excepción, excepto para capturarla en un nivel superior y registrarla. Y no tiene sentido crear una clase personalizada para eso.
De todos modos, eso es lo que hago, pero si hay una forma mejor y similarmente expresiva, me gustaría saber también. En mi propio código, desde que dejé de codificar C hace mucho tiempo, nunca devuelvo un NSError, incluso si una API me pasa uno.
@throw([NSException exceptionWith…])
enfoque ya que es más conciso.