lanzando una excepción en el objetivo-c / cacao

Respuestas:

528

Yo uso de la [NSException raise:format:]siguiente manera:

[NSException raise:@"Invalid foo value" format:@"foo of %d is invalid", foo];
e.James
fuente
99
Prefiero de esta manera como se aprueba el @throw([NSException exceptionWith…])enfoque ya que es más conciso.
Sam Soffes
99
Asegúrese de leer la advertencia importante de daños ( stackoverflow.com/questions/324284/324805#324805 )
e.James
26
Generalmente prefiero esto también, pero hay un problema. Podría ser mi versión actual de Xcode, pero la sintaxis [NSException raise ...] no parece ser reconocida por el analizador como una ruta de salida de un método que devuelve un valor. Veo la advertencia "El control puede llegar al final de la función no nula" cuando se utiliza esta sintaxis, pero con la sintaxis @throw ([NSException exceptionWith ...]), el analizador reconoce eso como una salida y no muestra la advertencia.
mattorb
1
@mpstx Siempre uso la sintaxis de lanzamiento por la razón que has dado (que todavía es relevante dos años después en Xcode 4.6, y probablemente siempre lo será). Hacer que el IDE reconozca que lanzar una excepción es un punto de salida de la función con frecuencia es importante si desea evitar advertencias.
Mark Amery
FWIW Estoy notando que los bloques @ try / @ catch también dan como resultado falsos negativos para las advertencias de "el control llega al final de la función no nula" (es decir, la advertencia no se muestra cuando debería ser)
Brian Gerstle
256

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 .

daños
fuente
11
Creo que esto se aplica a la mayoría de los lenguajes de programación: "trate de evitar el uso de excepciones para situaciones de error comunes". Lo mismo se aplica en Java; Es una mala práctica manejar los errores de entrada del usuario (por ejemplo) con excepciones. No solo por el uso de recursos, sino también por la claridad del código.
beetstra
66
Más importante aún, las excepciones en Cocoa están diseñadas para indicar errores de programa no recuperables. Hacer lo contrario se ejecuta en el marco y puede conducir a un comportamiento indefinido. Consulte stackoverflow.com/questions/3378696/iphone-try-end-try/… para más detalles.
KPM
99
"Lo mismo se aplica en Java;" Discrepar. Puede usar excepciones marcadas en Java muy bien para condiciones de error normales. Por supuesto, no usarías excepciones de tiempo de ejecución.
Daniel Ryan
Prefiero la forma de Cocoa (las excepciones son solo para errores de programador), así que prefiero hacerlo también en Java, pero la realidad es que debe seguir las prácticas típicas en un entorno, y las excepciones para el manejo de errores se destacan como un mal olor en Objective-C, pero se usan mucho para ese propósito en Java.
gnasher729
1
Este comentario no responde la pregunta. Tal vez el OP solo quiera bloquear la aplicación para probar si el marco del informe de bloqueo funciona como se esperaba.
Simon
62
@throw([NSException exceptionWith…])

Xcode reconoce las @throwdeclaraciones como puntos de salida de funciones, como las returndeclaraciones. El uso de la @throwsintaxis evita las advertencias erróneas de " Control puede llegar al final de la función no nula " que puede obtener [NSException raise:…].

Además, @throwse puede usar para lanzar objetos que no son de la clase NSException.

Peter Hosey
fuente
11
@Steph Thirion: Vea developer.apple.com/documentation/Cocoa/Conceptual/Exceptions/… para todos los detalles. ¿Línea de fondo? Ambos funcionarán, pero @throw se puede usar para lanzar objetos que no son de la clase NSException.
e.James
33

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.

Daniel Yankowsky
fuente
1
Sí, esto es correcto (después de 4 años: D), cree su propia clase de error ABCError que se extiende desde la clase NSError y úsela para excepciones comprobadas en lugar de NSExceptions. Aumente NSExceptions donde ocurren los errores del programador (situación inesperada, como un problema de formato de número).
chathuram
15

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

rustyshelf
fuente
Todavía tengo un bloqueo por excepción de tiempo de ejecución en el bloque try
famfamfam
14

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.

Psicópata
fuente
¡Bueno saber! Tenemos una biblioteca de terceros que no queremos modificar que arroja excepciones incluso para los errores más pequeños. Tenemos que atraparlos en un lugar de la aplicación y eso nos hace sentirnos avergonzados, pero esto me hace sentir un poco mejor.
Yuri Brigance
8

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

Jason Fuerstenberg
fuente
Este comentario no responde la pregunta. Tal vez el OP solo quiera bloquear la aplicación para probar si el marco del informe de bloqueo funciona como se esperaba.
Simon
7

Así es como lo aprendí de "The Big Nerd Ranch Guide (4th edition)":

@throw [NSException exceptionWithName:@"Something is not right exception"
                               reason:@"Can't perform this operation because of this or that"
                             userInfo:nil];
Johannes
fuente
OK, pero no dice mucho sobre el userInfo:nil. :)
Cœur
6

Puede usar dos métodos para generar excepciones en el bloque try catch

@throw[NSException exceptionWithName];

o el segundo método

NSException e;
[e raise];
Subbu
fuente
3

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

R. van Twisk
fuente
0

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.

gnasher729
fuente
0

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

}

}

Aleksandr B.
fuente
-7

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.

Usuario eliminado
fuente
44
Recomendaría intentar programar un servidor con excepciones como parte del flujo normal de casos de error antes de hacer declaraciones tan generales como "no hay razón para no usar excepciones normalmente en el objetivo C". Lo creas o no, hay razones para escribir aplicaciones de alto rendimiento (o al menos partes de aplicaciones) en ObjC, y lanzar excepciones normalmente obstaculiza seriamente el rendimiento.
jbenet
66
De hecho, hay muy buenas razones para no utilizar excepciones en Cocoa. Vea la respuesta de Bill Bumgarner aquí para más información: stackoverflow.com/questions/3378696/iphone-try-end-try/… . Él sabe de lo que está hablando (pista: consulte a su empleador). Las excepciones en Cocoa se tratan como errores irrecuperables y pueden dejar el sistema en un estado inestable. NSError es el camino a seguir para pasar errores generales.
Brad Larson
Las excepciones son excepcionales . Las fallas de las reglas de negocios seguramente no califican. "Encontrar y diseñar un código con una gran cantidad de excepciones puede resultar en un rendimiento decente". MSDN a través de codinghorror.com/blog/2004/10/…
Jonathan Watmough
3
No se pueden lanzar excepciones desde bloques. Las excepciones generadas en un entorno ARC pueden hacer que su programa se filtre. De ahí el voto negativo.
Moszi
"No importa cuán costoso sea lanzar y atrapar una excepción" Estoy escribiendo un emulador donde el rendimiento es crítico. No puedo lanzar un montón de costosas excepciones.
NobodyNada