web-dev-qa-db-fra.com

Comment utiliser NSError dans mon application iPhone?

Je travaille sur la détection des erreurs dans mon application et je cherche à utiliser NSError. Je suis un peu confus quant à la façon de l'utiliser et de la remplir.

Quelqu'un pourrait-il donner un exemple de la façon dont je remplis alors utiliser NSError?

225
Nic Hubbard

Eh bien, ce que je fais habituellement, c’est que mes méthodes qui peuvent provoquer une erreur lors de l’exécution prennent une référence à un pointeur NSError. Si quelque chose ne va vraiment pas dans cette méthode, je peux renseigner la référence NSError avec des données d'erreur et renvoyer nil à partir de la méthode.

Exemple:

- (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;
}

Nous pouvons alors utiliser la méthode comme ceci. Ne même pas la peine d'inspecter l'objet d'erreur à moins que la méthode ne retourne 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.

Nous avons pu accéder à l'erreur localizedDescription de l'erreur car nous avons défini une valeur pour NSLocalizedDescriptionKey.

Le meilleur endroit pour obtenir plus d'informations est documentation d'Apple . C'est vraiment bien.

Il existe également un didacticiel simple et agréable sur Cocoa Is My Girlfriend .

470
Alex

Je voudrais ajouter quelques suggestions supplémentaires basées sur ma mise en œuvre la plus récente. J'ai consulté du code de Apple et je pense que mon code se comporte de la même manière.

Les articles ci-dessus expliquent déjà comment créer des objets NSError et les renvoyer, je ne vais donc pas m'embêter avec cette partie. Je vais juste essayer de suggérer un bon moyen d'intégrer des erreurs (codes, messages) dans votre propre application.


Je recommande de créer 1 en-tête qui donnera un aperçu de toutes les erreurs de votre domaine (application, bibliothèque, etc.). Mon en-tête actuel ressemble à ceci:

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";

Désormais, lorsque vous utilisez les valeurs ci-dessus pour les erreurs, Apple crée un message d'erreur standard de base pour votre application. Une erreur pourrait être créée comme suit:

+ (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;
}

Le message d'erreur standard généré par Apple (error.localizedDescription) pour le code ci-dessus se présentera comme suit:

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

Ce qui précède est déjà très utile pour un développeur, car le message affiche le domaine dans lequel l'erreur s'est produite et le code d'erreur correspondant. Les utilisateurs finaux n’auront aucune idée de la signification du code d'erreur 1002, nous devons donc maintenant implémenter des messages Nice pour chaque code.

Pour les messages d'erreur, nous devons garder à l'esprit la localisation (même si nous ne mettons pas en œuvre les messages localisés). J'ai utilisé l'approche suivante dans mon projet actuel:


1) Créez un fichier strings qui contiendra les erreurs. Les fichiers de chaînes sont facilement localisables. Le fichier pourrait ressembler à ceci:

FSError.strings

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

2) Ajoutez des macros pour convertir les codes entiers en messages d'erreur localisés. J'ai utilisé 2 macros dans mon fichier Constants + Macros.h. J'inclus toujours ce fichier dans l'en-tête de préfixe (MyApp-Prefix.pch) pour plus de commodité.

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) Il est maintenant facile d'afficher un message d'erreur convivial basé sur un code d'erreur. Un exemple:

UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Error" 
            message:FS_ERROR_LOCALIZED_DESCRIPTION(error.code) 
            delegate:nil 
            cancelButtonTitle:@"OK" 
            otherButtonTitles:nil];
[alert show];
54
Wolfgang Schreurs

Super réponse Alex. Un problème potentiel est la déréférence NULL. Référence d'Apple sur Création et renvoi d'objets 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;
...
37
jlmendezbonini

Objective-C

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

Swift

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

S'il vous plaît se référer à la suite tutoriel

j'espère que cela vous sera utile, mais vous devez au préalable lire la documentation de NSError

C'est un lien très intéressant que j'ai trouvé récemment ErrorHandling

9
Tirth

Un autre modèle de conception que j'ai vu implique l'utilisation de blocs, ce qui est particulièrement utile lorsqu'une méthode est exécutée de manière asynchrone.

Supposons que les codes d'erreur suivants soient définis:

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

Vous définiriez votre méthode qui peut générer une erreur comme ceci:

- (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);
    }
}

Et ensuite, lorsque vous l'appelez, vous n'avez pas à vous soucier de la déclaration de l'objet NSError (l'achèvement du code le fera pour vous) ni de la vérification de la valeur renvoyée. Vous pouvez simplement fournir deux blocs: un qui sera appelé lorsqu'il y aura une exception et un qui sera appelé quand il réussira:

[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");
    }
}];
3
Senseful

Je vais essayer de résumer la bonne réponse d’Alex et le point de jlmendezbonini, en ajoutant une modification qui rendra tout ce qui est compatible avec ARC (ce n’est pas encore le cas, car ARC se plaindra puisque vous devriez retourner id, ce qui signifie "tout objet", mais BOOL n'est pas un type d'objet).

- (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;
}

Maintenant, au lieu de rechercher la valeur de retour de notre appel de méthode, nous vérifions si error est toujours nil. Si ce n'est pas nous avons un problème.

// 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.
3
Gabriele Petronella

C'est un peu hors de propos, mais si vous n'avez pas d'option pour NSError, vous pouvez toujours afficher l'erreur de bas niveau:

 NSLog(@"Error = %@ ",[NSString stringWithUTF8String:strerror(errno)]);
0
Mike.R