Objective-C succinctement exceptions et erreurs

En Objective-C, deux types d’erreurs peuvent survenir pendant l’exécution d’un programme.. Inattendu Les erreurs sont des erreurs "graves" de programmation qui entraînent généralement la fermeture prématurée de votre programme. Ceux-ci s'appellent exceptions, car ils représentent une condition exceptionnelle dans votre programme. D'autre part, attendu des erreurs se produisent naturellement au cours de l'exécution d'un programme et peuvent être utilisées pour déterminer le succès d'une opération. Ceux-ci sont appelés les erreurs.

Vous pouvez également aborder la distinction entre exceptions et erreurs comme une différence entre leurs publics cibles. En général, les exceptions servent à informer le programmeur à propos de quelque chose qui a mal tourné, alors que les erreurs sont utilisées pour informer le utilisateur qu'une action demandée n'a pu être complétée.

Flux de contrôle pour les exceptions et les erreurs

Par exemple, tenter d'accéder à un index de tableau qui n'existe pas constitue une exception (une erreur du programmeur), alors que ne pas ouvrir un fichier est une erreur (une erreur de l'utilisateur). Dans le premier cas, quelque chose s'est probablement mal passé dans le déroulement de votre programme et il devrait probablement s'arrêter peu de temps après l'exception. Dans ce dernier cas, vous voudrez dire à l'utilisateur que le fichier ne peut pas être ouvert et éventuellement demander de réessayer l'action, mais rien n'empêche votre programme de continuer à s'exécuter après l'erreur.


Gestion des exceptions

Le principal avantage des capacités de traitement des exceptions d'Objective-C est la possibilité de séparer le traitement des erreurs de la détection des erreurs. Lorsqu'une partie du code rencontre une exception, elle peut la "jeter" vers le bloc de traitement d'erreur le plus proche, qui peut "attraper" des exceptions spécifiques et les gérer de manière appropriée. Le fait que des exceptions puissent être levées depuis des emplacements arbitraires évite d'avoir à vérifier en permanence les messages de réussite ou d'échec de chaque fonction impliquée dans une tâche particulière..

le @essayer, @capture(), et @enfin Les directives du compilateur sont utilisées pour intercepter et gérer les exceptions, et @jeter directive est utilisée pour les détecter. Si vous avez travaillé avec des exceptions en C #, ces structures de gestion des exceptions devraient vous être familières..

Il est important de noter qu'en Objective-C, les exceptions sont relativement lentes. En conséquence, leur utilisation devrait être limitée à la détection des erreurs de programmation graves, et non pour le contrôle de base. Si vous essayez de déterminer quoi faire en fonction d'un attendu erreur (par exemple, ne pas charger un fichier), veuillez vous référer au Section de gestion des erreurs.

La classe NSException

Les exceptions sont représentées comme des instances du NSException classe ou une sous-classe de celle-ci. C'est un moyen pratique d'encapsuler toutes les informations nécessaires associées à une exception. Les trois propriétés qui constituent une exception sont décrites comme suit:

  • prénom - Une instance de NSString qui identifie de manière unique l'exception.
  • raison - Une instance de NSString contenant une description de l'exception lisible par l'homme.
  • informations utilisateur - Une instance de NSDictionary qui contient des informations spécifiques à l'application liées à l'exception.

Le framework Foundation définit plusieurs constantes qui définissent les noms d'exception "standard". Ces chaînes peuvent être utilisées pour vérifier quel type d'exception a été intercepté.

Vous pouvez également utiliser le initWithName: raison: userInfo: Méthode d'initialisation pour créer de nouveaux objets d'exception avec vos propres valeurs. Les objets d'exception personnalisés peuvent être capturés et lancés à l'aide des mêmes méthodes que celles décrites dans les sections suivantes..

Générer des exceptions

Commençons par examiner le comportement par défaut d'un programme en matière de gestion des exceptions. le objectAtIndex: méthode de NSArray est défini pour lancer un NSRangeException (une sous-classe de NSException) lorsque vous essayez d'accéder à un index qui n'existe pas. Donc, si vous demandez le 10th élément d'un tableau qui n'a que trois éléments, vous aurez une exception à expérimenter:

#importation  int main (int argc, const char * argv []) @autoreleasepool NSArray * crew = [tableau NSArray arrayWithObjects: @ "Dave", @ "Heywood", @ "Frank", nil]; // Cela jettera une exception. NSLog (@ "% @", [crew objectAtIndex: 10]);  retourne 0; 

Lorsqu'il rencontre une exception non interceptée, Xcode interrompt le programme et vous indique la ligne à l'origine du problème..

Abandon d'un programme en raison d'une exception non interceptée

Ensuite, nous allons apprendre à intercepter les exceptions et à empêcher le programme de se terminer..

Catching Exceptions

Pour gérer une exception, tout code qui peut aboutir à une exception devrait être placé dans un @essayer bloc. Ensuite, vous pouvez intercepter des exceptions spécifiques en utilisant le @capture() directif. Si vous devez exécuter un code de maintenance, vous pouvez éventuellement le placer dans un @enfin bloc. L'exemple suivant montre ces trois directives de gestion des exceptions:

@try NSLog (@ "% @", [crew objectAtIndex: 10]);  @catch (exception NSException *) NSLog (@ "Une exception capturée"); // Nous allons simplement ignorer l'exception.  @finally NSLog (@ "Nettoyage en place"); 

Cela devrait générer les éléments suivants dans votre console Xcode:

Pris une exception! Nom: NSRangeException Raison: *** - [__ NSArrayI objectAtIndex:]: index 10 au-delà des limites [0… 2] Nettoyage en cours

Quand le programme rencontre le [objectAtIndex équipage: 10] message, il jette un NSRangeException, qui est pris dans le @capture() directif. À l'intérieur de la @capture() block est l'endroit où l'exception est réellement gérée. Dans ce cas, nous affichons simplement un message d'erreur descriptif, mais dans la plupart des cas, vous voudrez probablement écrire du code pour résoudre le problème..

Lorsqu'une exception est rencontrée dans le @essayer bloc, le programme passe à la mémoire correspondante @capture() bloc, qui signifie n'importe quel code après l'exception survenue ne sera pas exécutée. Cela pose un problème si le @essayer block a besoin d’être nettoyé (par exemple, s’il a ouvert un fichier, ce fichier doit être fermé). le @enfin bloc résout ce problème, car il est garanti à exécuter indépendamment du fait qu'une exception se soit produite ou non. C’est donc l’endroit idéal pour attacher les bouts du vêtement. @essayer bloc.

Les parenthèses après la @capture() directive vous permet de définir le type d’exception que vous essayez d’attraper. Dans ce cas, c'est un NSException, qui est la classe d'exception standard. Mais, une exception peut être réellement tout classe-pas seulement un NSException. Par exemple, le suivant @capture() directive manipulera un objet générique:

@catch (id genericException)

Nous allons apprendre à lancer des exemples de NSException ainsi que des objets génériques dans la section suivante.

Lancer des exceptions

Lorsque vous détectez une condition exceptionnelle dans votre code, vous créez une instance de NSException et le remplir avec les informations pertinentes. Ensuite, vous le lancez en utilisant le bien nommé @jeter directive, invitant le plus proche @essayer/@capture bloquer pour le gérer.

Par exemple, l'exemple suivant définit une fonction permettant de générer des nombres aléatoires entre un intervalle spécifié. Si l'appelant passe un intervalle non valide, la fonction lève une erreur personnalisée..

#importation  int generateRandomInteger (int minimum, int maximum) if (minimum> = maximum) // Créez l'exception. NSException * exception = [NSException exceptionWithName: @ "RandomNumberIntervalException" raison: @ "*** generateRandomInteger ():" "paramètre maximal non supérieur au paramètre minimal" userInfo: nil]; // Lance l'exception. @throw exception;  // Retourne un entier aléatoire. return arc4random_uniform ((maximum - minimum) + 1) + minimum;  int main (int argc, const char * argv []) @autoreleasepool int result = 0; @try resultat = generateRandomInteger (0, 10);  @catch (exception NSException *) NSLog (@ "Problème !!! Exception capturée:% @", [nom de l'exception]);  NSLog (@ "Nombre aléatoire:% i", résultat);  retourne 0; 

Depuis ce code passe un intervalle valide (0, 10) à generateRandomInteger (), il n'y aura pas d'exception à attraper. Cependant, si vous modifiez l’intervalle comme suit (0, -10), vous aurez la chance de voir le @capture() bloquer en action. C’est essentiellement ce qui se passe sous le capot lorsque les classes du cadre rencontrent des exceptions (par exemple, le NSRangeException élevé par NSArray).

Il est également possible de relancer les exceptions que vous avez déjà capturées. Ceci est utile si vous voulez être informé qu'une exception particulière s'est produite mais que vous ne voulez pas nécessairement le gérer vous-même. Pour plus de commodité, vous pouvez même omettre l’argument de la @jeter directif:

@try resultat = generateRandomInteger (0, -10);  @catch (exception NSException *) NSLog (@ "Problème !!! Exception capturée:% @", [nom de l'exception]); // Rejette l'exception en cours. @jeter 

Cela transmet l'exception interceptée au gestionnaire immédiatement supérieur, qui est dans ce cas le gestionnaire d'exceptions de niveau supérieur. Cela devrait afficher la sortie de notre @capture() bloquer, ainsi que la valeur par défaut Fermeture de l'application en raison d'une exception non interceptée… message suivi d'une sortie abrupte.

le @jeter directive n'est pas limitée à NSException objets-il peut jeter littéralement tout objet. L’exemple suivant jette un NSNumber objet au lieu d'une exception normale. Notez également que vous pouvez cibler différents objets en ajoutant plusieurs @capture() déclarations après la @essayer bloc:

#importation  int generateRandomInteger (int minimum, int maximum) if (minimum> = maximum) // Génère un nombre en utilisant l'intervalle "par défaut". NSNumber * suppose = [NSNumber numberWithInt: generateRandomInteger (0, 10)]; // Lancer le nombre. @ throw suppose;  // Retourne un entier aléatoire. return arc4random_uniform ((maximum - minimum) + 1) + minimum;  int main (int argc, const char * argv []) @autoreleasepool int result = 0; @try result = generateRandomInteger (30, 10);  @catch (NSNumber * suppose) NSLog (@ "Avertissement: intervalle par défaut utilisé"); résultat = [devine intValue];  @catch (exception NSException *) NSLog (@ "Problème !!! Exception capturée:% @", [nom de l'exception]);  NSLog (@ "Nombre aléatoire:% i", résultat);  retourne 0; 

Au lieu de jeter un NSException objet, generateRandomInteger () essaie de générer un nouveau nombre entre certaines limites "par défaut". L'exemple vous montre comment @jeter peut travailler avec différents types d'objets, mais à proprement parler, ce n'est pas la meilleure conception d'application, ni l'utilisation la plus efficace des outils de gestion des exceptions d'Objective-C. Si vous vouliez vraiment utiliser la valeur renvoyée comme le code précédent, vous feriez mieux de procéder à une vérification conditionnelle simple et ancienne à l'aide de NSError, comme discuté dans la section suivante.

En outre, certains des cadres de base d'Apple attendre un NSException objet à lancer, alors soyez prudent avec les objets personnalisés lors de l'intégration avec les bibliothèques standard.


La gestion des erreurs

Alors que les exceptions sont conçues pour informer les programmeurs lorsque quelque chose ne va pas, les erreurs sont conçues pour être un moyen simple et efficace de vérifier si une action a réussi ou non. Contrairement aux exceptions, les erreurs sont conçu pour être utilisé dans vos relevés de flux de contrôle quotidiens.

La classe NSError

La seule chose que les erreurs et les exceptions ont en commun est qu'elles sont toutes deux implémentées en tant qu'objets. le NSError La classe encapsule toutes les informations nécessaires à la représentation des erreurs:

  • code - Un NSInteger qui représente l'identifiant unique de l'erreur.
  • domaine - Une instance de NSString définir le domaine de l'erreur (décrit plus en détail dans la section suivante).
  • informations utilisateur - Une instance de NSDictionary qui contient des informations spécifiques à l'application liées à l'erreur. Ceci est généralement utilisé beaucoup plus que le informations utilisateur dictionnaire de NSException.

En plus de ces attributs de base, NSError stocke également plusieurs valeurs conçues pour faciliter le rendu et le traitement des erreurs. Ce sont en réalité des raccourcis dans la informations utilisateur dictionnaire décrit dans la liste précédente.

  • description localisée - Un NSString contenant la description complète de l'erreur, qui comprend généralement le motif de l'échec. Cette valeur est généralement affichée à l'utilisateur dans un panneau d'alerte..
  • localizedFailureReason - Un NSString contenant une description autonome de la raison de l'erreur. Ceci est utilisé uniquement par les clients qui veulent isoler la raison de l'erreur de sa description complète.
  • récupérationSuggestion - Un NSString indiquant à l'utilisateur comment récupérer l'erreur.
  • options de récupération localisées - Un NSArray des titres utilisés pour les boutons de la boîte de dialogue d'erreur. Si ce tableau est vide, un seul D'accord bouton est affiché pour ignorer l'alerte.
  • helpAnchor - Un NSString pour afficher lorsque l'utilisateur appuie sur la Aidez-moi bouton d'ancrage dans un panneau d'alerte.

Comme avec NSException, la initWithDomain: code: userInfo méthode peut être utilisée pour initialiser personnalisé NSError les instances.

Domaines d'erreur

Un domaine d'erreur est comme un espace de noms pour les codes d'erreur. Les codes doivent être uniques dans un même domaine, mais ils peuvent se chevaucher avec les codes d'autres domaines. En plus d'empêcher les collisions de code, les domaines fournissent également des informations sur l'origine de l'erreur. Les quatre principaux domaines d'erreur intégrés sont les suivants: NSMachErrorDomain, NSPOSIXErrorDomain, NSOSStatusErrorDomain, et NSCocoaErrorDomain. le NSCocoaErrorDomain contient les codes d'erreur de nombreux frameworks Objective-C standard d'Apple; Cependant, certains cadres définissent leurs propres domaines (par exemple,., NSXMLParserErrorDomain).

Si vous devez créer des codes d’erreur personnalisés pour vos bibliothèques et vos applications, vous devez toujours les ajouter à le tien domaine d'erreur-ne jamais étendre l'un des domaines intégrés. Créer votre propre domaine est un travail relativement trivial. Comme les domaines ne sont que des chaînes, il vous suffit de définir une constante de chaîne qui ne soit en conflit avec aucun des autres domaines d'erreur de votre application. Apple suggère que les domaines prennent la forme de com...ErreurDomaine.

Capturer les erreurs

Il n'y a pas de construction de langage dédiée à la manipulation NSError instances (bien que plusieurs classes intégrées soient conçues pour les gérer). Ils sont conçus pour être utilisés avec des fonctions spécialement conçues qui renvoient un objet quand ils réussissent et néant quand ils échouent. La procédure générale de capture des erreurs est la suivante:

  1. Déclarer un NSError variable. Vous n'avez pas besoin de l'allouer ou de l'initialiser.
  2. Transmettez cette variable sous la forme d'un double pointeur sur une fonction peut entraîner une erreur. En cas de problème, la fonction utilisera cette référence pour enregistrer des informations sur l'erreur..
  3. Vérifiez la valeur de retour de cette fonction en cas de succès ou d'échec. Si l'opération a échoué, vous pouvez utiliser NSError pour gérer l'erreur vous-même ou l'afficher à l'utilisateur.

Comme vous pouvez le voir, une fonction ne fonctionne généralement pas revenir un NSError object-it retourne la valeur qu'il est supposé atteindre s'il réussit, sinon il retourne néant. Vous devez toujours utiliser la valeur de retour d'une fonction pour détecter les erreurs. N'utilisez jamais la présence ou l'absence d'un NSError objet pour vérifier si une action a réussi. Les objets d'erreur sont uniquement supposés décrire une erreur potentielle, pas vous dire si une erreur s'est produite.

L'exemple suivant illustre un cas d'utilisation réaliste pour NSError. Il utilise une méthode de chargement de fichier de NSString, ce qui est en fait en dehors de la portée du livre. le iOS succinctement Ce livre traite en détail de la gestion de fichiers, mais pour le moment, concentrons-nous sur les capacités de traitement des erreurs d'Objective-C.

Tout d'abord, nous générons un chemin de fichier pointant vers ~ / Desktop / SomeContent.txt. Ensuite, nous créons un NSError référence et le passer à la stringWithContentsOfFile: encoding: error: méthode permettant de capturer des informations sur les erreurs éventuelles lors du chargement du fichier. Notez que nous passons un référence au *Erreur pointeur, ce qui signifie que la méthode demande un pointeur vers un pointeur (c'est-à-dire un double pointeur). Cela permet à la méthode de renseigner la variable avec son propre contenu. Enfin, nous vérifions la valeur de retour (pas l'existence du Erreur variable) pour voir si stringWithContentsOfFile: encoding: error: réussi ou pas. Si tel est le cas, vous pouvez travailler avec la valeur stockée dans le contenu variable; sinon, nous utilisons le Erreur variable pour afficher des informations sur ce qui a mal tourné.

#importation  int main (int argc, const char * argv []) @autoreleasepool // Génère le chemin du fichier souhaité. NSString * filename = @ "SomeContent.txt"; NSArray * path = NSSearchPathForDirectoriesInDomains (NSDesktopDirectory, NSUserDomainMask, YES); NSString * desktopDir = [path objectAtIndex: 0]; NSString * path = [desktopDir stringByAppendingPathComponent: nom_fichier]; // Essayez de charger le fichier. NSError * error; NSString * content = [NSString stringWithContentsOfFile: codage du chemin: erreur NSUTF8StringEncoding: & error]; // Vérifiez si cela a fonctionné. if (content == nil) // Une erreur est survenue. NSLog (@ "Erreur lors du chargement du fichier% @!", Chemin); NSLog (@ "Description:% @", [erreur localiséeDescription]); NSLog (@ "Reason:% @", [erreur localizedFailureReason]);  else // Contenu chargé avec succès. NSLog (@ "Contenu chargé!"); NSLog (@ "% @", contenu);  retourne 0; 

Depuis le ~ / Desktop / SomeContent.txt Le fichier n'existe probablement pas sur votre ordinateur, ce code entraînera probablement une erreur. Tout ce que vous avez à faire pour réussir le chargement est de créer SomeContent.txt sur votre bureau.

Erreurs personnalisées

Les erreurs personnalisées peuvent être configurées en acceptant un double pointeur sur un NSError objet et le remplir vous-même. N'oubliez pas que votre fonction ou méthode doit renvoyer un objet ou néant, selon qu’il réussisse ou échoue (ne retournez pas le NSError référence).

L’exemple suivant utilise une erreur au lieu d’une exception pour atténuer les paramètres non valides dans generateRandomInteger () une fonction. Remarquerez que **Erreur est un double pointeur qui nous permet de renseigner la variable sous-jacente à partir de la fonction. Il est très important de vérifier que l'utilisateur a bien passé un test valide. **Erreur paramètre avec si (erreur! = NULL). Vous devriez toujours le faire dans vos propres fonctions générant des erreurs. Depuis le **Erreur paramètre est un double pointeur, nous pouvons attribuer une valeur à la variable sous-jacente via *Erreur. Et encore une fois, nous vérifions les erreurs en utilisant le valeur de retour (si (résultat == nul)), pas le Erreur variable.

#importation  NSNumber * generateRandomInteger (int minimum, int maximum, erreur NSError **) if (minimum> = maximum) if (error! = NULL) // Créez l'erreur. NSString * domain = @ "com.MyCompany.RandomProject.ErrorDomain"; int errorCode = 4; NSMutableDictionary * userInfo = [dictionnaire NSMutableDictionary]; [userInfo setObject: @ "Le paramètre maximal n'est pas supérieur au paramètre minimal" forKey: NSLocalizedDescriptionKey]; // Remplit la référence d'erreur. * erreur = [[NSError alloc] initWithDomain: code de domaine: errorCode userInfo: userInfo];  return nil;  // Retourne un entier aléatoire. return [NSNumber numberWithInt: arc4random_uniform ((maximum - minimum) + 1) + minimum];  int main (int argc, const char * argv []) @autoreleasepool NSError * error; NSNumber * result = generateRandomInteger (0, -10, & error); if (result == nil) // Vérifiez ce qui ne va pas. NSLog (@ "Une erreur s'est produite!"); NSLog (@ "Domaine:% @ Code:% li", [domaine d'erreur], [code d'erreur]); NSLog (@ "Description:% @", [erreur localiséeDescription]);  else // Coffre-fort pour utiliser la valeur renvoyée. NSLog (@ "Nombre aléatoire:% i", [result intValue]);  retourne 0; 

La totalité de la description localisée, localizedFailureReason, et propriétés associées de NSError sont effectivement stockés dans son informations utilisateur dictionnaire utilisant des clés spéciales définies par NSLocalizedDescriptionKey, NSLocalizedFailureReasonErrorKey, etc. Ainsi, tout ce que nous avons à faire pour décrire l’erreur est d’ajouter des chaînes aux clés appropriées, comme indiqué dans le dernier exemple..

Généralement, vous souhaiterez définir des constantes pour les domaines d'erreur et les codes personnalisés afin qu'ils soient cohérents d'une classe à l'autre..


Résumé

Ce chapitre a fourni une discussion détaillée des différences entre les exceptions et les erreurs. Les exceptions sont conçues pour informer les programmeurs de problèmes graves dans leur programme, tandis que les erreurs représentent une action utilisateur ayant échoué. En règle générale, une application prête à la production devrait ne pas Lancer des exceptions, sauf en cas de circonstances vraiment exceptionnelles (manque de mémoire dans un périphérique, par exemple).

Nous avons couvert l'utilisation de base de NSError, mais gardez à l'esprit qu'il existe plusieurs classes intégrées dédiées au traitement et à l'affichage des erreurs. Malheureusement, ce sont tous des composants graphiques et sortent donc du cadre de ce livre. le iOS succinctement suite a une section dédiée sur l'affichage et la récupération des erreurs.

Dans le dernier chapitre de Objective-C succinctement, nous aborderons l'un des sujets les plus déroutants d'Objective-C. Nous allons découvrir comment les blocs nous traitent fonctionnalité de la même manière que nous traitons Les données. Cela aura un impact considérable sur les possibilités d'une application Objective-C..

Cette leçon représente un chapitre de Objective-C Succinctly, un eBook gratuit de l’équipe de Syncfusion..