web-dev-qa-db-fra.com

Exceptions Objective-C

Je viens de terminer un cours de programmation d'applications iPhone. Dans le cadre du cours, j'ai vu

  • Objective-C fournit une gestion des exceptions à l'aide de @try directive
  • La bibliothèque système n'utilise pas la gestion des exceptions, préférant à return nil

J'ai demandé si je devais utiliser la gestion des exceptions pour le nouveau code que j'ai écrit (par exemple, si j'écris à la fois le code frontal et le code logique, de sorte que la communication entre eux est entre mes mains) mais on m'a dit que non, il ne fallait pas utiliser d'exceptions pour les nouveaux code. (Mais il n'a pas élaboré, puis la classe a continué, j'ai pensé que peut-être la raison deviendrait claire plus tard ..)

Les exceptions sont sûrement supérieures à return nil? Vous pouvez attraper des types particuliers, vous n'êtes pas tenté de les ignorer en ignorant le type de retour d'une fonction qui réussit normalement, vous avez des messages texte qui peuvent être enregistrés, ils permettent à votre code de se concentrer sur le cas normal et donc d'être plus lisible. Pourquoi utiliser des exceptions .

Alors, qu'est-ce que tu penses? Mon entraîneur avait-il raison de ne pas utiliser d'exceptions Objective-C? Si oui, pourquoi?

52
Adrian Smith

Il n'est pas sûr de lever des exceptions dans des circonstances où les ressources ne sont pas gérées automatiquement. C'est le cas du framework Cocoa (et des frameworks voisins), car ils utilisent le comptage manuel des références.

Si vous lancez une exception, tout appel release que vous sautez en déroulant la pile entraînera une fuite. Cela ne devrait vous limiter que si vous êtes certain de ne pas récupérer car toutes les ressources sont retournées au système d'exploitation à la fin d'un processus.

Malheureusement, NSRunLoops ont tendance à intercepter toutes les exceptions qui se propagent vers eux, donc si vous lancez pendant un événement, vous reprendrez à l'événement suivant. C'est évidemment très mauvais. Par conséquent, il vaut mieux que vous ne jetiez tout simplement pas.

Ce problème est diminué si vous utilisez l'Objective-C récupéré, car toute ressource représentée par un objet Objective-C sera correctement libérée. Cependant, les ressources C (telles que les descripteurs de fichiers ou la mémoire allouée malloc) qui ne sont pas encapsulées dans un objet Objective-C continueront à fuir.

Donc, dans l'ensemble, ne jetez pas.

L'API Cocoa a plusieurs solutions à cela, comme vous l'avez mentionné. Renvoyer nil et le NSError** pattern en sont deux.

Clarifications pour ARC

Les utilisateurs d'ARC peuvent choisir d'activer ou de désactiver la sécurité d'exception complète. Lorsque la sécurité des exceptions est activée, ARC génère du code pour libérer des références fortes lorsque leur portée est supprimée, ce qui permet d'utiliser en toute sécurité l'exception dans votre code . ARC ne corrigera pas les bibliothèques externes pour activer la prise en charge des exceptions dans celles-ci, vous devez donc faire attention où vous lancez (et surtout où vous interceptez), même avec la prise en charge des exceptions activée dans votre programme.

La prise en charge des exceptions ARC peut être activée avec -fobjc-arc-exceptions ou désactivé avec -fno-objc-arc-exceptions. Par défaut, il est désactivé dans Objective-C mais activé dans Objective-C++.

La sécurité complète des exceptions dans Objective-C est désactivée par défaut, car les auteurs de Clang supposent que les programmes Objective-C ne récupéreront pas de toute façon une exception, et parce qu'il y a un coût de taille de code important et une petite pénalité de performance associée à ce nettoyage. Dans Objective-C++, en revanche, C++ introduit déjà beaucoup de code de nettoyage, et les gens sont beaucoup plus susceptibles d'avoir réellement besoin d'une sécurité d'exception.

Tout cela provient de la spécification ARC sur le site Web de LLVM .

65
zneak

Dans la programmation Cocoa et iOS, des exceptions sont utilisées pour indiquer une erreur de programmation non récupérable. Lorsqu'une exception est levée par les frameworks, cela indique que les frameworks ont détecté un état d'erreur qui n'est pas à la fois récupérable et pour lequel l'état interne n'est plus défini.

De plus, les exceptions levées via le code de framework laissent les frameworks dans un état indéfini. C'est à dire. vous ne pouvez pas faire quelque chose comme:

void a() {
    @throw [MyException exceptionWithName: @"OhNoes!"  ....];
}

@try {
    ... call into framework code that calls a() above ...
} @catch (MyException e) {
    ... handle e ...
}

Conclusion:

Les exceptions ne doivent pas être utilisées dans Cocoa pour le contrôle de flux, la validation des entrées utilisateur, la détection de la validité des données ou pour indiquer autrement des erreurs récupérables. Pour cela, vous utilisez le NSError** modèle comme documenté ici (merci Abizem).

(Notez qu'il existe un petit nombre d'API qui violent cela - où une exception est utilisée pour indiquer un état récupérable. Des bogues ont été déposés contre ceux-ci pour déprécier et éventuellement supprimer ces incohérences.)


Enfin a trouvé le document Je cherchais:

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 un message non valide et la perte de la connexion au serveur Windows. Vous vous occupez généralement de ces sortes d'erreurs avec des exceptions lors de la création d'une application plutôt qu'à l'exécution.

Si vous disposez d'un corps de code existant (tel qu'une bibliothèque tierce) qui utilise des exceptions pour gérer les conditions d'erreur, vous pouvez utiliser le code tel quel dans votre application Cocoa. Mais vous devez vous assurer que les exceptions d'exécution attendues ne s'échappent pas de ces sous-systèmes et finissent dans le code de l'appelant. Par exemple, une bibliothèque d'analyse peut utiliser des exceptions en interne pour indiquer des problèmes et permettre une sortie rapide d'un état d'analyse qui pourrait être profondément récursif; cependant, vous devez prendre soin d'attraper ces exceptions au niveau supérieur de la bibliothèque et de les traduire en un code ou état de retour approprié.

34
bbum

Je pense, et d'autres me corrigeront si je me trompe, que les exceptions doivent être utilisées pour détecter les erreurs du programmeur, tandis que la gestion des erreurs de type NSError doit être utilisée pour des conditions exceptionnelles qui se produisent pendant l'exécution du programme.

Et quant au retour de nil, ce n'est pas tout - les fonctions qui pourraient avoir des problèmes ne renvoient pas seulement un nil, elles peuvent également (et devraient) fournir des informations supplémentaires en utilisant l'objet NSError qui est transmis en tant que paramètre.

Voir également

7
Abizern

Personnellement, je ne vois aucune raison de ne pas utiliser d'exceptions dans votre propre code. Le modèle d'exception est plus propre pour la gestion des erreurs que

  • ayant toujours une valeur de retour,
  • en s'assurant que l'une des valeurs de retour possibles signifie vraiment "erreur"
  • en passant un paramètre supplémentaire qui est une référence à un NSError*
  • saupoudrer votre code avec du code de nettoyage pour chaque retour d'erreur ou avoir des gotos explicites pour passer à une section de nettoyage d'erreur commune.

Cependant, vous devez garder à l'esprit que le code des autres n'est pas garanti pour gérer correctement l'exception (s'il s'agit de C pur, en fait, il ne peut pas gérer correctement). Ainsi, vous ne devez jamais permettre à une exception de se propager au-delà des limites de votre code. Par exemple, si vous jetez une exception profondément dans les entrailles d'une méthode déléguée, vous devez gérer cette exception avant de retourner à l'expéditeur du message délégué.

3
JeremyP

Le type de gestion des erreurs utilisé dépend de quoi que vous essayez d'accomplir. Presque toutes les méthodes d'Apple rempliront un objet NSError accessible en cas d'erreur.

Ce type de gestion des erreurs peut être utilisé en conjonction avec un bloc try-catch dans une section de code:

-(NSDictionary *)getDictionaryWithID:(NSInteger)dictionaryID error:(NSError **)error
{
    @try
    {
         //attempt to retrieve the dictionary
    }
    @catch (NSException *e)
    {
        //something went wrong
        //populate the NSError object so it can be accessed
    }
}

En résumé, l'objectif de la méthode détermine le type de gestion des erreurs que vous devez utiliser. Mais, dans cet exemple ci-dessus, vous pouvez utiliser les deux.

Une utilisation courante du bloc try-catch:

@try
{
    [dictionary setObject:aNullObject forKey:@"Key"];
}
@catch (NSException *e)
{
    //one of the objects/keys was NULL
    //this could indicate that there was a problem with the data source
}

J'espère que cela t'aides.

2
Evan Mulawski

Je pense que votre entraîneur a raison. Vous pouvez faire toutes sortes d'arguments pour et contre les exceptions, mais en fin de compte, si vous voulez que votre code "sent" directement chez un développeur Cocoa expérimenté, vous implémenterez votre application en utilisant les mêmes modèles de conception que Apple utiliser dans leur code et dans leurs frameworks, ce qui signifie nils et NSErrors plutôt que des exceptions.

Les avantages de ne pas utiliser d'exceptions concernent donc principalement la cohérence et la familiarité.

Une autre chose à considérer est qu'un nil dans l'objectif C est généralement assez "sûr". Autrement dit, vous pouvez envoyer n'importe quel message à un nil et votre application ne se bloquera pas.

(Je dois également souligner que Apple do utilise des exceptions à certains endroits, généralement lorsque vous utilisez une API de manière incorrecte.)

1
Stephen Darlington

si vous préférez des exceptions, vous pouvez les utiliser. si vous le faites, je vous recommande de les utiliser avec parcimonie car il devient très difficile à maintenir (points de sortie supplémentaires que vous devrez généralement exercer au moment de l'exécution pour prouver l'exactitude du programme).

il n'y a pas de spécification pour les attentes de gestion des exceptions pour les clients; ils doivent maintenir leur programme basé sur les documents (fastidieux, sujet aux erreurs, ne sera probablement pas maintenu jusqu'à ce qu'une exception soit levée).

si je devais les utiliser, je ne les utiliserais que dans de très rares cas et utiliserais des codes d'erreur ou retournerais zéro si possible. De plus, je les utiliserais en interne dans une bibliothèque et n'exposerais pas les clients aux exceptions dans l'interface (ou aux effets secondaires). je ne les utilise pas en objc ou c ++ (enfin, je les attraperai mais je ne les lancerai pas).

comme vous vous en doutez, vous devez éviter de les utiliser pour contrôler le déroulement du programme (une mauvaise utilisation courante).

il vaut mieux les utiliser lorsque vous devez échapper à un certain crash. si vous allez de l'avant et écrivez le code maintenant, je vous recommande d'écrire simplement l'interface pour gérer les erreurs dans le cadre du flux du programme et éviter d'écrire des exceptions si possible.

correction au message d'origine: les bibliothèques de cacao préfèrent renvoyer zéro, dans certains cas, elles lèveront des exceptions pour vous (pour attraper).

0
justin