Quelle est la meilleure façon de lever une exception en objectif-c / cacao?
Quelle est la meilleure façon de lever une exception en objectif-c / cacao?
Réponses:
J'utilise [NSException raise:format:]
comme suit:
[NSException raise:@"Invalid foo value" format:@"foo of %d is invalid", foo];
Un mot de prudence ici. Dans Objective-C, contrairement à de nombreux langages similaires, vous devez généralement éviter d'utiliser des exceptions pour les situations d'erreur courantes qui peuvent se produire en fonctionnement normal.
Documentation d'Apple pour Obj-C 2.0 stipule ce qui suit: "Important: Les exceptions sont gourmandes en ressources dans Objective-C. Vous ne devez pas utiliser d'exceptions pour le contrôle de flux général, ou simplement pour signifier des erreurs (comme un fichier non accessible)"
Documentation de gestion des exceptions conceptuelles d'Apple explique la même chose, mais avec plus de mots: "Important: vous devez réserver l'utilisation d'exceptions pour la programmation ou les erreurs d'exécution inattendues telles que l'accès à la collection hors limites, les tentatives de mutation d'objets immuables, l'envoi d'un message non valide et la perte de la connexion au serveur Windows. En règle générale, vous vous occupez de ce type d'erreurs avec des exceptions lors de la création d'une application plutôt qu'au moment de l'exécution. [.....] Au lieu d'exceptions, les objets d'erreur (NSError) et le Le mécanisme de remise des erreurs Cocoa est le moyen recommandé de communiquer les erreurs attendues dans les applications Cocoa. "
Cela s'explique en partie par le respect des idiomes de programmation dans Objective-C (en utilisant des valeurs de retour dans des cas simples et des paramètres de référence (souvent la classe NSError) dans des cas plus complexes), en partie parce que lever et intercepter des exceptions est beaucoup plus cher et enfin (et perplexe le plus important) que les exceptions Objective-C sont un wrapper mince autour des fonctions setjmp () et longjmp () de C, gâchant essentiellement votre gestion prudente de la mémoire, voir cette explication .
@throw([NSException exceptionWith…])
Xcode reconnaît les @throw
instructions comme des points de sortie de fonction, comme les return
instructions. L'utilisation de la @throw
syntaxe évite les avertissements erronés "Le contrôle peut atteindre la fin de la fonction non nulle " que vous pouvez obtenir [NSException raise:…]
.
En outre, @throw
peut être utilisé pour lancer des objets qui ne sont pas de NSException de classe.
Concernant [NSException raise:format:]
. Pour ceux qui viennent d'un arrière-plan Java, vous vous souviendrez que Java fait la distinction entre Exception et RuntimeException. L'exception est une exception vérifiée et RuntimeException n'est pas cochée. En particulier, Java suggère d'utiliser des exceptions vérifiées pour les "conditions d'erreur normales" et des exceptions non vérifiées pour les "erreurs d'exécution causées par une erreur de programmation". Il semble que les exceptions Objective-C devraient être utilisées aux mêmes endroits que vous utiliseriez une exception non vérifiée, et les valeurs de retour de code d'erreur ou les valeurs NSError sont préférées aux endroits où vous utiliseriez une exception vérifiée.
Je pense que pour être cohérent, il est plus agréable d'utiliser @throw avec votre propre classe qui étend NSException. Ensuite, vous utilisez les mêmes notations pour essayer finalement catch:
@try {
.....
}
@catch{
...
}
@finally{
...
}
Apple explique ici comment lever et gérer les exceptions: Attraper les exceptions Lancer les exceptions
Depuis ObjC 2.0, les exceptions Objective-C ne sont plus un wrapper pour setjmp () longjmp () de C, et sont compatibles avec l'exception C ++, le @try est "gratuit", mais lever et intercepter des exceptions est beaucoup plus cher.
Quoi qu'il en soit, les assertions (en utilisant NSAssert et la famille de macros NSCAssert) lèvent NSException, et cela est sensé de les utiliser comme états Ries.
Utilisez NSError pour communiquer les échecs plutôt que les exceptions.
Points rapides sur NSError:
NSError permet aux codes d'erreur de style C (entiers) d'identifier clairement la cause racine et, espérons-le, au gestionnaire d'erreurs de surmonter l'erreur. Vous pouvez très facilement encapsuler les codes d'erreur des bibliothèques C comme SQLite dans les instances NSError.
NSError a également l'avantage d'être un objet et offre un moyen de décrire l'erreur plus en détail avec son membre de dictionnaire userInfo.
Mais le meilleur de tous, NSError NE PEUT PAS être lancé, ce qui encourage une approche plus proactive de la gestion des erreurs, contrairement à d'autres langues qui jettent simplement la patate chaude de plus en plus haut dans la pile d'appels, auquel cas elle ne peut être signalée qu'à l'utilisateur et pas traité de manière significative (pas si vous croyez qu'il faut respecter le plus grand principe de la POO en matière d'information qui se cache).
Voici comment je l'ai appris 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];
userInfo:nil
. :)
Je pense que vous ne devez jamais utiliser d'exceptions pour contrôler le déroulement normal du programme. Mais des exceptions doivent être levées chaque fois qu'une valeur ne correspond pas à une valeur souhaitée.
Par exemple, si une fonction accepte une valeur, et que cette valeur n'est jamais autorisée à être nulle, alors c'est bien de trow une exception plutôt que d'essayer de faire quelque chose de 'intelligent' ...
Ries
Vous ne devez lever des exceptions que si vous vous trouvez dans une situation qui indique une erreur de programmation et que vous souhaitez arrêter l'application en cours d'exécution. Par conséquent, la meilleure façon de lever des exceptions est d'utiliser les macros NSAssert et NSParameterAssert et de vous assurer que NS_BLOCK_ASSERTIONS n'est pas défini.
Exemple de code pour la casse: @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);
}
}
En utilisant:
[self parseError:error completionBlock:^(NSString *error) {
NSLog(@"%@", error);
}];
Un autre cas d'utilisation plus avancé:
- (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
}
}
Il n'y a aucune raison de ne pas utiliser les exceptions normalement dans l'objectif C même pour signifier des exceptions aux règles métier. Apple peut dire utiliser NSError qui s'en soucie. Obj C existe depuis longtemps et à un moment donné, TOUTE la documentation C ++ a dit la même chose. La raison pour laquelle le fait de lancer et d'attraper une exception coûte peu est que la durée de vie d'une exception est extrêmement courte et ... c'est une EXCEPTION pour le flux normal. Je n'ai jamais entendu personne dire quoi que ce soit de ma vie, l'homme que cette exception a mis longtemps à être levée et rattrapée.
En outre, il y a des gens qui pensent que l'objectif C lui-même est trop cher et code à la place en C ou C ++. Donc, dire toujours utiliser NSError est mal informé et paranoïaque.
Mais la question de ce fil n'a pas encore été répondue quelle est la meilleure façon de lever une exception. Les moyens de retourner NSError sont évidents.
Il en est de même: [NSException raise: ... @throw [[NSException alloc] initWithName .... ou @throw [[MyCustomException ...?
J'utilise ici la règle cochée / décochée légèrement différemment que ci-dessus.
La vraie différence entre (en utilisant la métaphore java ici) cochée / décochée est importante -> si vous pouvez récupérer de l'exception. Et par récupérer, je veux dire non seulement PAS planter.
J'utilise donc des classes d'exceptions personnalisées avec @throw pour les exceptions récupérables, car il est probable que j'aurai une méthode d'application recherchant certains types d'échecs dans plusieurs blocs @catch. Par exemple, si mon application est un guichet automatique, j'aurais un bloc @catch pour la "WithdrawalRequestExceedsBalanceException".
J'utilise NSException: lever pour les exceptions d'exécution car je n'ai aucun moyen de récupérer à partir de l'exception, sauf pour l'attraper à un niveau supérieur et l'enregistrer. Et il n'y a aucun intérêt à créer une classe personnalisée pour cela.
Quoi qu'il en soit, c'est ce que je fais, mais s'il y a une meilleure manière expressive, j'aimerais aussi savoir. Dans mon propre code, depuis que j'ai arrêté de coder C il y a longtemps, je ne retourne jamais de NSError même si je suis passé par une API.
@throw([NSException exceptionWith…])
approche car elle est plus concise.