En tant que développeur Java qui lit la documentation Objective-C 2.0 d'Apple: je me demande ce que "envoyer un message à nil" signifie - sans parler de la façon dont il est réellement utile. Extrait de la documentation:
Il existe plusieurs modèles dans Cocoa qui tirent parti de ce fait. La valeur renvoyée d'un message à nil peut également être valide:
- Si la méthode renvoie un objet, tout type de pointeur, tout scalaire entier de taille inférieure ou égale à sizeof (void *), un flottant, un double, un double long ou un long long, alors un message envoyé à nil renvoie 0 .
- Si la méthode renvoie 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 struct ne seront pas remplis de zéros.
- Si la méthode renvoie autre chose que les types de valeurs susmentionnés, la valeur de retour d'un message envoyé à nil n'est pas définie.
Java a-t-il rendu mon cerveau incapable de cogner l'explication ci-dessus? Ou y a-t-il quelque chose qui me manque qui rendrait cela aussi clair que du verre?
Je reçois l'idée de messages/récepteurs dans Objective-C, je suis simplement confus à propos 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 dans Java qui imprime tous les éléments dans 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 ceci: someObject.foo (NULL); vous obtiendrez probablement une NullPointerException quand il essaiera d'accéder à list, dans ce cas dans l'appel à list.size (); Maintenant, vous n'appelleriez probablement jamais someObject.foo (NULL) avec la valeur NULL comme ça. Cependant, vous pouvez avoir obtenu votre ArrayList à partir d'une méthode qui renvoie NULL si elle rencontre 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 produira une NullPointerException. L'objet nil sera accessible en premier à [anArray count] Cependant, au lieu de lancer une NullPointerException, Objective-C renverra simplement 0 dans conformément aux règles ci-dessus, afin que la boucle ne s'exécute pas. Cependant, si nous la configurons pour s'exécuter un nombre défini de fois, nous envoyons d'abord un message à anArray à [anArray objectAtIndex: i]; cela renverra également 0, mais puisque objectAtIndex: retourne un pointeur et qu'un pointeur sur 0 est nul/NULL, NSLog sera passé nul à chaque fois dans la boucle. (Bien que NSLog soit une fonction et non une méthode, il affiche (null) s'il est passé) un NSString nul.
Dans certains cas, il est préférable d'avoir une exception NullPointerException, car vous pouvez tout de suite dire que quelque chose ne va pas avec le programme, mais à moins que vous ne remarquiez l'exception, le programme se bloquera. (En C, essayer de déréférencer NULL de cette manière entraîne le blocage du programme.) En Objective-C, il provoque simplement un comportement au moment de l'exécution éventuellement incorrect. Cependant, si vous avez une méthode qui ne casse pas si elle retourne 0/nil/NULL/une structure mise à zéro, cela vous évite d'avoir à vérifier pour vous assurer que l'objet ou les paramètres 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'objet pouvant accepter un sélecteur est une cible valide pour ce sélecteur.
Cela enregistre BEAUCOUP de "l'objet cible est-il de type X?" code - tant que l'objet récepteur implémente le sélecteur, cela fait absolument aucune différence de quelle classe il s'agit! nil
est un NSObject qui accepte n'importe quel sélecteur - il ne fait tout simplement rien do. Cela élimine également beaucoup de code "vérifier si rien, n'envoyez pas le message si vrai". (Le concept "s'il l'accepte, il l'implémente" est aussi ce qui vous permet de créer protocoles, qui sont un peu comme Java interfaces: une déclaration que si une classe implémente les méthodes indiquées, puis elle se conforme au protocole.)
La raison en est d'éliminer le code singe qui ne fait rien sauf garder le compilateur heureux. Oui, vous obtenez la surcharge d'un appel de méthode supplémentaire, mais vous économisez temps du programmeur, ce qui est une ressource beaucoup plus chère que le temps CPU. De plus, vous éliminez plus de code et plus de complexité conditionnelle de votre application.
Clarification pour les downvoters: vous pensez peut-être que ce n'est pas une bonne façon de procéder, mais c'est la façon 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 pour iPhone).
Cela signifie que le runtime ne produit pas d'erreur lorsque objc_msgSend est appelé sur le pointeur nil; au lieu de cela, il renvoie une valeur (souvent utile). Les messages qui pourraient 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
C'est-à-dire que nil semble être le tableau vide. Masquer une référence NSView nulle ne fait rien. Pratique, hein?
Dans la citation de la documentation, il y a deux concepts distincts - il serait peut-être préférable que la documentation le clarifie:
Il existe plusieurs modèles dans Cocoa qui tirent parti de ce fait.
La valeur renvoyée d'un message à nil peut également être valide:
Le premier est probablement plus pertinent ici: être capable d'envoyer des messages à nil
rend le code plus simple - vous n'avez pas besoin de vérifier les valeurs nulles partout. L'exemple canonique est probablement la méthode 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 (que les valeurs renvoyées d'un message à nil
sont également généralement valides), cependant, ajoute un effet multiplicateur au premier. Par exemple:
if ([myArray count] > 0) {
// do something...
}
Encore une fois, ce code ne nécessite pas de vérification des valeurs de nil
et s'écoule naturellement ...
Cela dit, la flexibilité supplémentaire qui permet d'envoyer des messages à nil
a un coût. Il est possible que vous écriviez à un moment donné du code qui échoue d'une manière particulière parce que vous n'avez pas pris en compte la possibilité qu'une valeur soit nil
.
De Greg Parker 's site :
Si vous exécutez LLVM Compiler 3.0 (Xcode 4.2) ou version ultérieure
Messages à zéro avec type de retour | return Entiers jusqu'à 64 bits | 0 Virgule flottante jusqu'au double long | 0,0 Pointeurs | néant Structs | {0} Tout type _Complex | {0, 0}
Cela signifie souvent ne pas avoir à vérifier la présence d'objets nuls partout pour des raisons de sécurité - en particulier:
[someVariable release];
ou, comme indiqué, diverses méthodes de comptage et de longueur renvoient toutes 0 lorsque vous avez une valeur nulle, vous n'avez donc pas à ajouter de vérifications supplémentaires pour zéro partout:
if ( [myString length] > 0 )
ou ca:
return [myArray count]; // say for number of rows in a table
Les messages ObjC qui sont envoyés à nil et dont les valeurs de retour ont une taille supérieure à sizeof (void *) produisent des valeurs non définies sur les processeurs PowerPC. En plus de cela, ces messages provoquent le retour de valeurs non définies dans des champs de structures dont la taille est supérieure à 8 octets sur les processeurs Intel. Vincent Gable l'a bien décrit dans son article de blog
Ne pensez pas à "le récepteur étant nul"; Je suis d'accord, que est assez bizarre. Si vous envoyez un message à zéro, il n'y a pas de récepteur. Vous envoyez simplement un message à rien.
Comment gérer 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.
Je ne pense pas que les autres réponses l'aient mentionné clairement: si vous êtes habitué à Java, vous devez garder à l'esprit que même si Objective-C sur Mac OS X prend en charge la gestion des exceptions, c'est une fonctionnalité de langage facultative qui peut être activé/désactivé avec un indicateur de compilation. Je suppose que cette conception de "l'envoi de messages à nil
est sûr" est antérieure à l'inclusion de la prise en charge de la gestion des exceptions dans le langage et a été effectuée avec un objectif similaire à l'esprit: les méthodes peuvent renvoyer nil
à indiquent des erreurs, et puisque l'envoi d'un message à nil
renvoie généralement nil
à son tour, cela permet à l'indication d'erreur de se propager à travers votre code afin que vous n'ayez pas à le vérifier à chaque message . Vous n'avez qu'à le vérifier aux points où cela compte. Personnellement, je pense que la propagation et la gestion des exceptions sont une meilleure façon d'atteindre cet objectif, mais tout le monde ne peut pas être d'accord avec cela. (D'un autre côté, par exemple, je n'aime pas l'exigence de Java sur la nécessité de déclarer les exceptions qu'une méthode peut lancer, ce qui vous oblige souvent à syntaxiquement propager des déclarations d'exception dans votre code; mais c'est une autre discussion.)
J'ai publié une réponse similaire, mais plus longue, à la question connexe "Est-ce que l'affirmation que chaque création d'objet a réussi est nécessaire dans l'objectif 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 rien de C en ajoutant nil. nil est un objet pointeur vers rien. Bien que sémantiquement distincts de NULL, ils sont techniquement équivalents les uns aux autres.
Les NSObjects nouvellement alloués commencent leur vie avec leur contenu défini sur 0. Cela signifie que tous les pointeurs de cet objet vers d'autres objets commencent par zéro, il n'est donc pas nécessaire, par exemple, de définir self. (Association) = nil dans les méthodes init.
Le comportement le plus notable de nil, cependant, est qu'il peut recevoir des messages.
Dans d'autres langages, comme C++ (ou Java), cela ferait planter votre programme, mais dans Objective-C, l'invocation d'une méthode sur nil renvoie une valeur nulle. Cela simplifie considérablement les expressions, car il évite de vérifier zéro avant de faire quoi que ce soit:
// For example, this expression...
if (name != nil && [name isEqualToString:@"Steve"]) { ... }
// ...can be simplified to:
if ([name isEqualToString:@"Steve"]) { ... }
Le fait de savoir comment nil fonctionne dans Objective-C permet à cette commodité d'être une fonctionnalité et non un bug caché dans votre application. Assurez-vous de vous prémunir contre les cas où des valeurs nulles sont indésirables, soit en vérifiant et en retournant tôt pour échouer en silence, soit en ajoutant un NSParameterAssert pour lever une exception.
Source: http://nshipster.com/nil/https://developer.Apple.com/library/ios/#documentation/cocoa/conceptual/objectivec/Chapters/ocObjectsClasses. html (Envoi de message à zéro).