En Objective-C, pourquoi [object doSomething]
? Ne serait-ce pas [*object doSomething]
puisque vous appelez une méthode sur l'objet ?, ce qui signifie que vous devez déréférencer le pointeur ?
Réponses
Trop de publicités?La réponse renvoie aux racines C de l'Objective-C. Objective-C a été écrit à l'origine comme un pré-processeur de compilateur pour le C. En d'autres termes, Objective-C n'était pas tant compilé qu'il était transformé en C pur et dur, puis compilé.
Commencez par la définition du type id
. Il est déclaré comme :
typedef struct objc_object {
Class isa;
} *id;
C'est-à-dire qu'un id
est un pointeur vers une structure dont le premier champ est de type Class (qui, lui-même, est un pointeur vers une structure qui définit une classe). Maintenant, considérons NSObject
:
@interface NSObject <NSObject> {
Class isa;
}
Notez que la mise en page de NSObject
et la disposition du type pointé par id
sont identiques. En effet, en réalité, une instance d'un objet Objective-C n'est qu'un pointeur vers une structure dont le premier champ - toujours un pointeur - pointe vers la classe qui contient les méthodes de cette instance (ainsi que d'autres métadonnées).
Lorsque vous sous-classez NSObject et ajoutez des variables d'instance, vous créez simplement, à toutes fins utiles, une nouvelle structure C qui contient vos variables d'instance sous forme de slots dans cette structure concaténée aux slots des variables d'instance de toutes les superclasses. (Le runtime moderne fonctionne légèrement différemment afin qu'une superclasse puisse avoir des ivars ajoutés sans exiger que toutes les sous-classes soient recompilées).
Maintenant, considérez la différence entre ces deux variables :
NSRect foo;
NSRect *bar;
(NSRect étant une simple structure C -- pas de ObjC impliqué). foo
est créé avec le stockage sur la pile. Il ne survivra pas une fois que le cadre de la pile sera fermé, mais vous n'aurez pas non plus à libérer de la mémoire. bar
est une référence à une structure NSRect qui a été, très probablement, créée sur le tas à l'aide de la fonction malloc()
.
Si vous essayez de dire :
NSArray foo;
NSArray *bar;
Le compilateur se plaindra de la première, en disant quelque chose du type Les objets basés sur des piles ne sont pas autorisés en Objective-C . En d'autres termes, tous Les objets Objective-C doivent être alloués depuis le tas (plus ou moins - il y a une ou deux exceptions, mais elles sont relativement ésotériques pour cette discussion) et, par conséquent, vous toujours font référence à un objet par l'intermédiaire de l'adresse de cet objet sur le tas ; vous travaillez toujours avec des pointeurs sur des objets (et la balise id
n'est en fait qu'un pointeur vers un vieil objet quelconque).
En revenant aux racines du langage, le préprocesseur C, vous pouvez traduire chaque appel de méthode en une ligne équivalente de C. Par exemple, les deux lignes de code suivantes sont identiques :
[myArray objectAtIndex: 42];
objc_msgSend(myArray, @selector(objectAtIndex:), 42);
De même, une méthode déclarée comme ceci :
- (id) objectAtIndex: (NSUInteger) a;
Est équivalent à une fonction C déclarée comme ceci :
id object_at_index(id self, SEL _cmd, NSUInteger a);
Et, en regardant objc_msgSend()
le premier argument est déclaré comme étant de type id
:
OBJC_EXPORT id objc_msgSend(id self, SEL op, ...);
Et c'est exactement pour ça que vous n'utilisez pas *foo
comme cible d'un appel de méthode. Faites la traduction à travers les formulaires ci-dessus -- l'appel à [myArray objectAtIndex: 42]
est traduit par l'appel de fonction C ci-dessus qui doit ensuite appeler quelque chose avec la déclaration d'appel de fonction C équivalente (le tout habillé en syntaxe de méthode).
La référence à l'objet est conservée car elle donne au messager -- objc_msgSend() l'accès à la classe pour trouver l'implémentation de la méthode -- et cette référence devient alors le premier paramètre -- le self -- de la méthode qui est finalement exécutée.
Si tu veux vraiment aller en profondeur, commencer ici . Mais ne vous inquiétez pas avant d'avoir pleinement grokked ce .
Tu ne devrais pas vraiment Pensez-y comme à des pointeurs vers des objets. Le fait qu'il s'agisse de pointeurs est une sorte de détail d'implémentation historique, et c'est ainsi qu'on les utilise dans la syntaxe d'envoi des messages (voir la réponse de @bbum). En fait, ce ne sont que des "identificateurs d'objets" (ou références). Rembobinons un peu pour voir le raisonnement conceptuel.
Objective-C a été proposé et discuté pour la première fois dans ce livre : Programmation orientée objet : Une approche évolutionniste . Ce n'est pas extrêmement pratique pour les programmeurs Cocoa modernes, mais les motivations du langage sont là.
Notez que dans le livre tous les objets sont de type id
. Vous ne voyez pas le plus spécifique Object *
dans le livre ; elles ne sont qu'une fuite dans l'abstraction lorsque nous parlons du "pourquoi". Voici ce que dit le livre :
Les identificateurs d'objets doivent identifier de manière unique tous les objets susceptibles de coexister dans le système à un moment donné. Ils sont stockés dans des variables locales, transmis en tant qu'arguments dans des expressions de messages et dans des appels de fonctions, détenus dans des variables d'instance (champs à l'intérieur des objets) et dans d'autres types de structures de mémoire. En d'autres termes, ils peuvent être utilisés de manière aussi fluide que les types intégrés du langage de base.
La manière dont un identifiant d'objet identifie réellement l'objet est un détail d'implémentation pour lequel de nombreux choix sont plausibles. Un choix raisonnable, certainement l'un des plus simples, et celui qui est utilisé en Objective-C, est d'utiliser l'adresse physique de l'objet en mémoire comme identifiant. Objective-C fait connaître cette décision au C en générant une déclaration typedef dans chaque fichier. Celle-ci définit un nouveau type, id, en fonction d'un autre type que le C comprend déjà, à savoir les pointeurs vers les structures. [...]
Un id consomme une quantité fixe d'espace. [...] Cet espace n'est pas le même que celui occupé par les données privées de l'objet lui-même.
(pp58-59, 2e éd.)
La réponse à votre question est donc double :
- La conception du langage spécifie que l'identifiant d'un objet n'est pas le même que l'objet lui-même, et que l'identifiant est la chose à laquelle vous envoyez des messages, pas l'objet lui-même.
- La conception ne dicte pas, mais suggère, l'implémentation que nous avons maintenant, où les pointeurs vers les objets sont utilisés comme identifiants.
La syntaxe strictement typée où l'on dit "un objet spécifiquement de type NSString" et où l'on utilise donc NSString *
est un changement plus moderne, et est fondamentalement un choix de mise en œuvre, équivalent à id
.
Si cela ressemble à une réponse hautaine à une question sur le déréférencement des pointeurs, il est important de garder à l'esprit que les objets en Objective-C sont " spéciaux " selon la définition du langage. Ils sont mis en œuvre comme des structures et transmis comme des pointeurs vers des structures, mais ils sont conceptuellement différents.
Parce que objc_msgSend() est déclaré comme ceci :
id objc_msgSend(id theReceiver, SEL theSelector, ...)
On ne déréférence jamais les pointeurs d'objets, point final. Le fait qu'ils soient typés comme des pointeurs plutôt que comme des "types d'objets" est un artefact de l'héritage C du langage. C'est exactement l'équivalent du système de types de Java, où les objets sont toujours accessibles par des références. Vous ne déréférencez jamais un objet en Java - en fait, vous ne le pouvez pas. Vous ne devez pas les considérer comme des pointeurs, car sémantiquement, ils ne le sont pas. Il s'agit simplement de références d'objets.