Envoi d’un message à zéro en Objective-C

En tant que développeur Java qui lit la documentation Objective-C 2.0 d’Apple: je me demande ce que signifie ” envoyer un message à zéro ” – sans parler de son utilité réelle. Prenant un extrait de la documentation:

Il existe plusieurs modèles dans Cocoa qui tirent parti de ce fait. La valeur renvoyée par un message à nil peut également être valide:

  • Si la méthode retourne un object, un type de pointeur, un scalaire entier de taille inférieure ou égale à sizeof (void *), un flottant, un double, un long double ou un long, un message envoyé à nil renvoie 0 .
  • Si la méthode retourne une structure, telle que définie par le Guide d’appel de fonction ABI de Mac OS X, à renvoyer dans les registres, un message envoyé à nil renvoie 0.0 pour chaque champ de la structure de données. Les autres types de données de structure ne seront pas remplis de zéros.
  • Si la méthode renvoie autre chose que les types de valeur susmentionnés, la valeur de retour d’un message envoyé à nil est indéfinie.

Est-ce que Java a rendu mon cerveau incapable d’expliquer l’explication ci-dessus? Ou y a-t-il quelque chose qui me manque qui rendrait cela aussi clair que le verre?

Je comprends l’idée de messages / récepteurs dans Objective-C, je suis simplement confus au sujet d’un récepteur qui se trouve être nil .

Eh bien, je pense que cela peut être décrit en utilisant un exemple très artificiel. Disons que vous avez une méthode en Java qui imprime tous les éléments d’une ArrayList:

 void foo(ArrayList list) { for(int i = 0; i < list.size(); ++i){ System.out.println(list.get(i).toString()); } } 

Maintenant, si vous appelez cette méthode comme suit: someObject.foo (NULL); vous allez probablement obtenir une exception NullPointerException quand il essaiera d'accéder à la liste, dans ce cas dans l'appel à list.size (); Maintenant, vous n'appellerez probablement jamais someObject.foo (NULL) avec la valeur NULL comme ça. Cependant, vous avez peut-être obtenu votre ArrayList à partir d'une méthode qui renvoie NULL si elle génère une erreur lors de la génération de ArrayList, comme someObject.foo (otherObject.getArrayList ());

Bien sûr, vous aurez également des problèmes si vous faites quelque chose comme ceci:

 ArrayList list = NULL; list.size(); 

Maintenant, dans Objective-C, nous avons la méthode équivalente:

 - (void)foo:(NSArray*)anArray { int i; for(i = 0; i < [anArray count]; ++i){ NSLog(@"%@", [[anArray objectAtIndex:i] stringValue]; } } 

Maintenant, si nous avons le code suivant:

 [someObject foo:nil]; 

Nous avons la même situation dans laquelle Java va produire une exception NullPointerException. On accédera d'abord à l'object nil à [anArray count] Cependant, au lieu de lancer une exception NullPointerException, Objective-C retournera simplement 0 conformément aux règles ci-dessus, de sorte que la boucle ne s'exécutera pas. Cependant, si nous définissons la boucle pour exécuter un nombre de fois défini, nous envoyons d'abord un message à anArray à [anArray objectAtIndex: i]; Cela renverra également 0, mais depuis objectAtIndex: renvoie un pointeur et un pointeur sur 0 est nul / NULL, NSLog sera transmis à chaque fois dans la boucle. (Bien que NSLog soit une fonction et non une méthode, elle imprime (null) si elle passe un NSSsortingng nul.

Dans certains cas, il est préférable d'avoir une exception NullPointerException, car vous pouvez dire immédiatement que quelque chose ne va pas avec le programme, mais à moins que vous ne renconsortingez l'exception, le programme va planter. (En C, essayer de déréférencer NULL de cette manière provoque le blocage du programme.) En Objective-C, cela provoque simplement un comportement d'exécution incorrect. Cependant, si vous avez une méthode qui ne se casse pas si elle retourne 0 / nil / NULL / une structure à zéro, cela vous évite d'avoir à vérifier que l'object ou les parameters sont nuls.

Un message à nil ne fait rien et renvoie nil , Nil , NULL , 0 ou 0.0 .

Tous les autres messages sont corrects, mais c’est peut-être le concept qui est important ici.

Dans les appels de méthode Objective-C, toute référence d’object pouvant accepter un sélecteur est une cible valide pour ce sélecteur.

Cela permet d’économiser beaucoup de “est l’object cible de type X?” code – tant que l’object récepteur implémente le sélecteur, la classe ne fait aucune différence ! nil est un NSObject qui accepte tout sélecteur – il ne fait rien. Cela élimine aussi beaucoup de “vérification de zéro, ne pas envoyer le message si vrai”. (Le concept “si il l’accepte, il l’implémente” est aussi ce qui vous permet de créer des protocoles , un peu comme les interfaces Java: une déclaration que si une classe implémente les méthodes indiquées, alors elle est conforme au protocole.)

La raison à cela est d’éliminer le code de singe qui ne fait rien sauf de garder le compilateur heureux. Oui, vous obtenez la surcharge d’un appel de méthode supplémentaire, mais vous économisez du temps de programmation , ce qui est une ressource beaucoup plus coûteuse que le temps CPU. De plus, vous éliminez plus de code et plus de complexité conditionnelle de votre application.

Préciser les downvoters: vous pensez peut-être que ce n’est pas une bonne solution, mais c’est la manière dont le langage est implémenté, et c’est l’idiome de programmation recommandé dans Objective-C (voir les conférences de programmation de Stanford sur iPhone).

Cela signifie que le moteur d’exécution ne génère pas d’erreur lorsque objc_msgSend est appelé sur le pointeur nil; au lieu de cela, il retourne une valeur (souvent utile). Les messages susceptibles d’avoir un effet secondaire ne font rien.

C’est utile car la plupart des valeurs par défaut sont plus appropriées qu’une erreur. Par exemple:

 [someNullNSArrayReference count] => 0 

Ie, nil semble être le tableau vide. Cacher une référence NSView nulle ne fait rien. Handy, hein?

Dans la citation de la documentation, il y a deux concepts distincts – il serait peut-être préférable que la documentation le rende plus clair:

Il existe plusieurs modèles dans Cocoa qui tirent parti de ce fait.

La valeur renvoyée par un message à nil peut également être valide:

Le premier est probablement plus pertinent ici: le fait d’être capable d’envoyer des messages à nil rend le code plus simple – vous n’avez pas à vérifier les valeurs NULL partout. L’exemple canonique est probablement la méthode de l’accesseur:

 - (void)setValue:(MyClass *)newValue { if (value != newValue) { [value release]; value = [newValue retain]; } } 

Si l’envoi de messages à nil n’était pas valide, cette méthode serait plus complexe – vous devriez avoir deux vérifications supplémentaires pour vous assurer que value et newValue ne sont pas nil avant de leur envoyer des messages.

Ce dernier point (les valeurs renvoyées par un message à nil sont également valides) ajoute un effet multiplicateur au premier. Par exemple:

 if ([myArray count] > 0) { // do something... } 

Ce code à nouveau ne nécessite pas de vérification des valeurs nil et circule naturellement …

Cela dit, la flexibilité supplémentaire de pouvoir envoyer des messages à nil des coûts. Il est possible que vous écriviez à un moment donné un code qui échoue de manière particulière, car vous n’avez pas pris en compte la possibilité qu’une valeur soit nil .

Du site de Greg Parker :

Si vous exécutez LLVM Comstackr 3.0 (Xcode 4.2) ou une version ultérieure

 Messages à nil avec le type de retour |  revenir
 Entiers jusqu'à 64 bits |  0
 Virgule flottante jusqu'à double long |  0.0
 Pointeurs |  néant
 Structs |  {0}
 Tout type _Complex |  {0, 0}

Cela signifie souvent ne pas avoir à chercher des objects nuls partout pour la sécurité – en particulier:

 [someVariable release]; 

ou, comme indiqué, diverses méthodes de comptage et de longueur renvoient toutes 0 si vous avez une valeur nulle, vous n’avez donc pas à append de vérifications supplémentaires pour tout ce qui est nul:

 if ( [mySsortingng length] > 0 ) 

ou ca:

 return [myArray count]; // say for number of rows in a table 

Ne pensez pas à “le récepteur étant nul”; Je suis d’accord, c’est assez bizarre. Si vous envoyez un message à zéro, il n’y a pas de destinataire. Vous envoyez juste un message à rien.

Comment faire face à cela est une différence philosophique entre Java et Objective-C: en Java, c’est une erreur; en Objective-C, c’est un no-op.

Les messages ObjC envoyés à nil et dont les valeurs de retour ont une taille supérieure à sizeof (void *) génèrent des valeurs indéfinies sur les processeurs PowerPC. En plus de cela, ces messages entraînent le retour de valeurs indéfinies dans les champs de structures dont la taille est également supérieure à 8 octets sur les processeurs Intel. Vincent Gable l’a bien décrit dans son blog

Je pense qu’aucune des autres réponses ne l’a clairement mentionné: si vous avez l’habitude de Java, gardez à l’esprit qu’Objective-C sous Mac OS X prend en charge la gestion des exceptions, mais qu’il s’agit d’une fonctionnalité de langage facultative. activé / désactivé avec un indicateur de compilateur. Je suppose que cette conception consistant à “envoyer des messages à nil est sûre” est antérieure à la prise en charge de la gestion des exceptions dans le langage et a été conçue avec un objective similaire: les méthodes peuvent renvoyer nil pour indiquer des erreurs retourne généralement nil tour à tour, cela permet à l’indication d’erreur de se propager dans votre code afin que vous n’ayez pas à le rechercher à chaque message. Vous n’avez qu’à le vérifier aux endroits où cela compte. Personnellement, je pense que la propagation et la gestion des exceptions sont un meilleur moyen d’atteindre cet objective, mais tout le monde n’est pas d’accord avec cela. (D’un autre côté, je n’aime pas, par exemple, l’exigence de Java de devoir déclarer quelles exceptions une méthode peut lancer, ce qui vous oblige souvent à propager syntaxiquement des déclarations d’exception dans votre code, mais c’est une autre discussion.)

J’ai posté une réponse similaire, mais plus longue, à la question connexe “Affirmer que chaque création d’object a réussi dans l’objective C?” si vous voulez plus de détails.

C ne représente rien comme 0 pour les valeurs primitives et NULL pour les pointeurs (ce qui équivaut à 0 dans un contexte de pointeur).

Objective-C s’appuie sur la représentation de C en ajoutant rien. nil est un pointeur d’object à rien. Bien que sémantiquement distincts de NULL, ils sont techniquement équivalents les uns aux autres.

Les NSObjects nouvellement alloués démarrent leur vie avec leur contenu défini sur 0. Cela signifie que tous les pointeurs que l’object a sur les autres objects commencent à être nuls, il est donc inutile, par exemple, de définir self. (Association) = nil dans les méthodes init.

Le comportement le plus notable de zéro, cependant, est qu’il peut recevoir des messages.

Dans d’autres langages, comme C ++ (ou Java), cela provoquerait un plantage de votre programme, mais en Objective-C, invoquer une méthode sur nil renvoie une valeur nulle. Cela simplifie grandement les expressions, car cela évite d’avoir à vérifier le néant avant de faire quoi que ce soit:

 // For example, this expression... if (name != nil && [name isEqualToSsortingng:@"Steve"]) { ... } // ...can be simplified to: if ([name isEqualToSsortingng:@"Steve"]) { ... } 

Être conscient de la façon dont Nil fonctionne dans Objective-C permet à cette commodité d’être une fonctionnalité, et non un bug caché dans votre application. Veillez à vous prémunir contre les cas où des valeurs nulles sont indésirables, soit en vérifiant et en renvoyant tôt pour échouer en mode silencieux, soit en ajoutant un NSParameterAssert pour lancer une exception.

Source: http://nshipster.com/nil/ https://developer.apple.com/library/ios/#documentation/cocoa/conceptual/objectivec/Chapters/ocObjectsClasses.html (Envoi du message à zéro).