¿Cómo puedo usar NSError en mi aplicación para iPhone?

228

Estoy trabajando en detectar errores en mi aplicación y estoy pensando en usarlos NSError. Estoy un poco confundido acerca de cómo usarlo y cómo poblarlo.

¿Podría alguien dar un ejemplo de cómo uso y luego uso NSError?

Nic Hubbard
fuente

Respuestas:

473

Bueno, lo que suelo hacer es que mis métodos que pueden generar errores en el tiempo de ejecución hagan referencia a un NSErrorpuntero. Si algo sale mal en ese método, puedo rellenar la NSErrorreferencia con datos de error y devolver nil desde el método.

Ejemplo:

- (id) endWorldHunger:(id)largeAmountsOfMonies error:(NSError**)error {
    // begin feeding the world's children...
    // it's all going well until....
    if (ohNoImOutOfMonies) {
        // sad, we can't solve world hunger, but we can let people know what went wrong!
        // init dictionary to be used to populate error object
        NSMutableDictionary* details = [NSMutableDictionary dictionary];
        [details setValue:@"ran out of money" forKey:NSLocalizedDescriptionKey];
        // populate the error object with the details
        *error = [NSError errorWithDomain:@"world" code:200 userInfo:details];
        // we couldn't feed the world's children...return nil..sniffle...sniffle
        return nil;
    }
    // wohoo! We fed the world's children. The world is now in lots of debt. But who cares? 
    return YES;
}

Entonces podemos usar el método como este. Ni siquiera se moleste en inspeccionar el objeto de error a menos que el método devuelva nil:

// initialize NSError object
NSError* error = nil;
// try to feed the world
id yayOrNay = [self endWorldHunger:smallAmountsOfMonies error:&error];
if (!yayOrNay) {
   // inspect error
   NSLog(@"%@", [error localizedDescription]);
}
// otherwise the world has been fed. Wow, your code must rock.

Pudimos acceder a los errores localizedDescriptionporque establecemos un valor para NSLocalizedDescriptionKey.

El mejor lugar para obtener más información es la documentación de Apple . Realmente es bueno.

También hay un tutorial simple y agradable sobre Cocoa Is My Girlfriend .

Alex
fuente
37
Este es el ejemplo más divertido de todos
ming yeow
Esta es una respuesta bastante impresionante, aunque hay algunos problemas en ARC y enviar el ida BOOL. Cualquier ligera variación compatible con ARC sería muy apreciada.
NSTJ
66
@TomJowett Me enojaría mucho si no pudiéramos terminar con el hambre en el mundo simplemente porque Apple nos empujó a mudarnos al nuevo mundo de ARC.
Manav
1
El tipo de retorno puede ser BOOL. Regrese NOen caso de error y en lugar de verificar el valor de retorno, simplemente verifique error. Si nilsigue adelante, si lo != nilmaneja.
Gabriele Petronella
8
-1: Realmente necesitas incorporar código que verifique que **errorno es nulo. De lo contrario, el programa arrojará un error que es completamente hostil y no hace evidente lo que está sucediendo.
FreeAsInBeer
58

Me gustaría agregar algunas sugerencias más basadas en mi implementación más reciente. He visto algunos códigos de Apple y creo que mi código se comporta de la misma manera.

Las publicaciones anteriores ya explican cómo crear objetos NSError y devolverlos, por lo que no me molestaré con esa parte. Solo intentaré sugerir una buena forma de integrar errores (códigos, mensajes) en su propia aplicación.


Recomiendo crear 1 encabezado que será una descripción general de todos los errores de su dominio (es decir, aplicación, biblioteca, etc.). Mi encabezado actual se ve así:

FSError.h

FOUNDATION_EXPORT NSString *const FSMyAppErrorDomain;

enum {
    FSUserNotLoggedInError = 1000,
    FSUserLogoutFailedError,
    FSProfileParsingFailedError,
    FSProfileBadLoginError,
    FSFNIDParsingFailedError,
};

FSError.m

#import "FSError.h" 

NSString *const FSMyAppErrorDomain = @"com.felis.myapp";

Ahora, al usar los valores anteriores para errores, Apple creará un mensaje de error estándar básico para su aplicación. Se podría crear un error como el siguiente:

+ (FSProfileInfo *)profileInfoWithData:(NSData *)data error:(NSError **)error
{
    FSProfileInfo *profileInfo = [[FSProfileInfo alloc] init];
    if (profileInfo)
    {
        /* ... lots of parsing code here ... */

        if (profileInfo.username == nil)
        {
            *error = [NSError errorWithDomain:FSMyAppErrorDomain code:FSProfileParsingFailedError userInfo:nil];            
            return nil;
        }
    }
    return profileInfo;
}

El mensaje de error estándar ( error.localizedDescription) generado por Apple para el código anterior tendrá el siguiente aspecto:

Error Domain=com.felis.myapp Code=1002 "The operation couldn’t be completed. (com.felis.myapp error 1002.)"

Lo anterior ya es bastante útil para un desarrollador, ya que el mensaje muestra el dominio donde ocurrió el error y el código de error correspondiente. Sin 1002embargo, los usuarios finales no tendrán idea de qué significa el código de error , por lo que ahora necesitamos implementar algunos mensajes agradables para cada código.

Para los mensajes de error, debemos tener en cuenta la localización (incluso si no implementamos mensajes localizados de inmediato). He utilizado el siguiente enfoque en mi proyecto actual:


1) crea un stringsarchivo que contendrá los errores. Los archivos de cadenas son fácilmente localizables. El archivo podría tener el siguiente aspecto:

FSError.strings

"1000" = "User not logged in.";
"1001" = "Logout failed.";
"1002" = "Parser failed.";
"1003" = "Incorrect username or password.";
"1004" = "Failed to parse FNID."

2) Agregue macros para convertir códigos enteros en mensajes de error localizados. He usado 2 macros en mi archivo Constantes + Macros.h. Siempre incluyo este archivo en el encabezado del prefijo ( MyApp-Prefix.pch) por conveniencia.

Constantes + Macros.h

// error handling ...

#define FS_ERROR_KEY(code)                    [NSString stringWithFormat:@"%d", code]
#define FS_ERROR_LOCALIZED_DESCRIPTION(code)  NSLocalizedStringFromTable(FS_ERROR_KEY(code), @"FSError", nil)

3) Ahora es fácil mostrar un mensaje de error fácil de usar basado en un código de error. Un ejemplo:

UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Error" 
            message:FS_ERROR_LOCALIZED_DESCRIPTION(error.code) 
            delegate:nil 
            cancelButtonTitle:@"OK" 
            otherButtonTitles:nil];
[alert show];
Wolfgang Schreurs
fuente
99
¡Gran respuesta! Pero, ¿por qué no poner la descripción localizada en el diccionario de información del usuario al que pertenece? [NSError errorWithDomain: FSMyAppErrorDomain code: FSProfileParsingFailedError userInfo: @ {NSLocalizedDescriptionKey: FS_ERROR_LOCALIZED_DESCRIPTION (error.code)}];
Richard Venable el
1
¿Hay algún lugar en particular donde debería poner el archivo de cadena? De FS_ERROR_LOCALIZED_DESCRIPTION () obtengo solo el número (código de error).
huggie
@huggie: no estoy muy seguro de lo que quieres decir. Por lo general, pongo estas macros que utilizo en toda la aplicación en un archivo llamado Constants+Macros.he importo este archivo en el encabezado ( .pcharchivo) del prefijo para que esté disponible en todas partes. Si quiere decir que solo está usando 1 de las 2 macros, eso podría funcionar. Quizás la conversión de inta NSStringno sea realmente necesaria, aunque no he probado esto.
Wolfgang Schreurs
@huggie: ow, creo que te entiendo ahora. Las cadenas deben estar en un archivo localizable ( .stringsarchivo), ya que ahí es donde se verá la macro de Apple. Lea sobre el uso NSLocalizedStringFromTableaquí: developer.apple.com/library/mac/documentation/cocoa/conceptual/…
Wolfgang Schreurs
1
@huggie: Sí, usé las tablas de cadenas localizadas. El código en la macro FS_ERROR_LOCALIZED_DESCRIPTIONverifica la cadena localizable en un archivo llamado FSError.strings. Es posible que desee consultar la guía de localización de .stringsarchivos de Apple si esto le resulta extraño.
Wolfgang Schreurs
38

Gran respuesta Alex. Un problema potencial es la desreferencia NULL. Referencia de Apple sobre creación y devolución de objetos NSError

...
[details setValue:@"ran out of money" forKey:NSLocalizedDescriptionKey];

if (error != NULL) {
    // populate the error object with the details
    *error = [NSError errorWithDomain:@"world" code:200 userInfo:details];
}
// we couldn't feed the world's children...return nil..sniffle...sniffle
return nil;
...
jlmendezbonini
fuente
30

C objetivo

NSError *err = [NSError errorWithDomain:@"some_domain"
                                   code:100
                               userInfo:@{
                                           NSLocalizedDescriptionKey:@"Something went wrong"
                               }];

Swift 3

let error = NSError(domain: "some_domain",
                      code: 100,
                  userInfo: [NSLocalizedDescriptionKey: "Something went wrong"])
AlBeebe
fuente
9

Consulte el siguiente tutorial

Espero que sea útil para usted, pero antes de que tenga que leer la documentación de NSError

Este es un enlace muy interesante que encontré recientemente ErrorHandling

Tirth
fuente
3

Trataré de resumir la gran respuesta de Alex y el punto de jlmendezbonini, agregando una modificación que hará que todo sea compatible con ARC (hasta ahora no es así, ya que ARC se quejará, ya que debe regresar id, lo que significa "cualquier objeto", pero BOOLno es un objeto tipo).

- (BOOL) endWorldHunger:(id)largeAmountsOfMonies error:(NSError**)error {
    // begin feeding the world's children...
    // it's all going well until....
    if (ohNoImOutOfMonies) {
        // sad, we can't solve world hunger, but we can let people know what went wrong!
        // init dictionary to be used to populate error object
        NSMutableDictionary* details = [NSMutableDictionary dictionary];
        [details setValue:@"ran out of money" forKey:NSLocalizedDescriptionKey];
        // populate the error object with the details
        if (error != NULL) {
             // populate the error object with the details
             *error = [NSError errorWithDomain:@"world" code:200 userInfo:details];
        }
        // we couldn't feed the world's children...return nil..sniffle...sniffle
        return NO;
    }
    // wohoo! We fed the world's children. The world is now in lots of debt. But who cares? 
    return YES;
}

Ahora, en lugar de verificar el valor de retorno de nuestra llamada al método, verificamos si errortodavía está nil. Si no es así, tenemos un problema.

// initialize NSError object
NSError* error = nil;
// try to feed the world
BOOL success = [self endWorldHunger:smallAmountsOfMonies error:&error];
if (!success) {
   // inspect error
   NSLog(@"%@", [error localizedDescription]);
}
// otherwise the world has been fed. Wow, your code must rock.
Gabriele Petronella
fuente
3
@Gabriela: Apple afirma que cuando se usan variables de indirección para devolver errores, el método en sí mismo siempre debe tener algún valor de retorno en caso de éxito o fracaso. Apple insta a los desarrolladores a verificar primero el valor de retorno y solo si el valor de retorno es de alguna manera inválido, verifique si hay errores. Consulte la página siguiente: developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/…
Wolfgang Schreurs
3

Otro patrón de diseño que he visto implica el uso de bloques, que es especialmente útil cuando un método se ejecuta de forma asincrónica.

Digamos que tenemos definidos los siguientes códigos de error:

typedef NS_ENUM(NSInteger, MyErrorCodes) {
    MyErrorCodesEmptyString = 500,
    MyErrorCodesInvalidURL,
    MyErrorCodesUnableToReachHost,
};

Definiría su método que puede generar un error así:

- (void)getContentsOfURL:(NSString *)path success:(void(^)(NSString *html))success failure:(void(^)(NSError *error))failure {
    if (path.length == 0) {
        if (failure) {
            failure([NSError errorWithDomain:@"com.example" code:MyErrorCodesEmptyString userInfo:nil]);
        }
        return;
    }

    NSString *htmlContents = @"";

    // Exercise for the reader: get the contents at that URL or raise another error.

    if (success) {
        success(htmlContents);
    }
}

Y luego, cuando lo llame, no necesita preocuparse por declarar el objeto NSError (la finalización del código lo hará por usted) o verificar el valor devuelto. Solo puede suministrar dos bloques: uno que se llamará cuando haya una excepción y otro que se llamará cuando tenga éxito:

[self getContentsOfURL:@"http://google.com" success:^(NSString *html) {
    NSLog(@"Contents: %@", html);
} failure:^(NSError *error) {
    NSLog(@"Failed to get contents: %@", error);
    if (error.code == MyErrorCodesEmptyString) { // make sure to check the domain too
        NSLog(@"You must provide a non-empty string");
    }
}];
Sensato
fuente
0

Bueno, está un poco fuera de alcance, pero en caso de que no tenga una opción para NSError, siempre puede mostrar el error de nivel bajo:

 NSLog(@"Error = %@ ",[NSString stringWithUTF8String:strerror(errno)]);
Mike.R
fuente
0
extension NSError {
    static func defaultError() -> NSError {
        return NSError(domain: "com.app.error.domain", code: 0, userInfo: [NSLocalizedDescriptionKey: "Something went wrong."])
    }
}

que puedo usar NSError.defaultError()siempre que no tenga un objeto de error válido.

let error = NSError.defaultError()
print(error.localizedDescription) //Something went wrong.
Hemang
fuente