Objective-C: différence entre id et void *

Réponses:

240

void * signifie "une référence à un morceau aléatoire de mémoire avec un contenu non typé / inconnu"

id signifie "une référence à un objet Objective-C aléatoire de classe inconnue"

Il existe des différences sémantiques supplémentaires:

  • Sous les modes GC Only ou GC Supported, le compilateur émettra des barrières d'écriture pour les références de type id, mais pas pour le type void *. Lors de la déclaration des structures, cela peut être une différence critique. Déclarer iVars comme void *_superPrivateDoNotTouch;provoquera une récolte prématurée d'objets s'il _superPrivateDoNotTouchs'agit en fait d'un objet. Ne fais pas ça.

  • tenter d'invoquer une méthode sur une référence de void *type provoquera un avertissement du compilateur.

  • tenter d'invoquer une méthode sur un idtype avertira uniquement si la méthode appelée n'a été déclarée dans aucune des @interfacedéclarations vues par le compilateur.

Ainsi, il ne faut jamais se référer à un objet comme un void *. De même, il faut éviter d'utiliser une idvariable typée pour faire référence à un objet. Utilisez la référence typée de classe la plus spécifique possible. Même NSObject *c'est mieux que idparce que le compilateur peut, au moins, fournir une meilleure validation des invocations de méthode par rapport à cette référence.

La seule utilisation courante et valide de void *est une référence de données opaque qui est transmise via une autre API.

Considérez la sortedArrayUsingFunction: context:méthode de NSArray:

- (NSArray *)sortedArrayUsingFunction:(NSInteger (*)(id, id, void *))comparator context:(void *)context;

La fonction de tri serait déclarée comme:

NSInteger mySortFunc(id left, id right, void *context) { ...; }

Dans ce cas, le NSArray transmet simplement ce que vous passez en tant contextqu'argument à la méthode en tant contextqu'argument. Il s'agit d'un morceau opaque de données de la taille d'un pointeur, en ce qui concerne NSArray, et vous êtes libre de l'utiliser à toutes fins utiles.

Sans une fonctionnalité de type de fermeture dans le langage, c'est le seul moyen de transporter un morceau de données avec une fonction. Exemple; si vous vouliez que mySortFunc () trie conditionnellement comme sensible à la casse ou insensible à la casse, tout en étant toujours thread-safe, vous passeriez l'indicateur is-case-sensitive dans le contexte, probablement en cours d'entrée et de sortie.

Fragile et sujet aux erreurs, mais le seul moyen.

Les blocs résolvent ce problème - Les blocs sont des fermetures pour C. Ils sont disponibles dans Clang - http://llvm.org/ et sont omniprésents dans Snow Leopard ( http://developer.apple.com/library/ios/documentation/Performance /Reference/GCD_libdispatch_Ref/GCD_libdispatch_Ref.pdf ).

bbum
la source
De plus, je suis à peu près sûr que an idest supposé répondre à -retainet -release, alors que a void*est complètement opaque pour l'appelé. Vous ne pouvez pas passer un pointeur arbitraire à -performSelector:withObject:afterDelay:(il conserve l'objet), et vous ne pouvez pas supposer que +[UIView beginAnimations:context:]cela conservera le contexte (le délégué d'animation doit conserver la propriété du contexte; UIKit conserve le délégué d'animation).
tc.
3
Aucune hypothèse ne peut être faite sur ce à quoi un idrépond. An idpeut facilement faire référence à une instance d'une classe qui n'est pas inhérente à NSObject. En pratique, cependant, votre déclaration correspond le mieux au comportement du monde réel; vous ne pouvez pas mélanger des <NSObject>classes non implémentées avec l'API Foundation et aller très loin, c'est certainement sûr!
bbum
2
Désormais id, les Classtypes sont traités comme un pointeur d'objet conservable sous ARC. Donc, l'hypothèse est vraie au moins sous ARC.
eonil
Qu'est-ce que «récolte prématurée»?
Brad Thomas
1
@BradThomas Lorsqu'un garbage collector collecte de la mémoire avant que le programme ne l'utilise.
bbum
21

id est un pointeur vers un objet objectif C, où as void * est un pointeur vers n'importe quoi.

id désactive également les avertissements liés à l'appel de mthods inconnus, par exemple:

[(id)obj doSomethingWeirdYouveNeverHeardOf];

ne donnera pas l'avertissement habituel sur les méthodes inconnues. Il lèvera, bien sûr, une exception au moment de l'exécution à moins que obj ne soit nul ou n'implémente vraiment cette méthode.

Souvent, vous devriez utiliser NSObject*ou id<NSObject>de préférence id, ce qui confirme au moins que l'objet retourné est un objet Cocoa, afin que vous puissiez utiliser en toute sécurité des méthodes telles que retenir / relâcher / autorelease dessus.

Peter N Lewis
la source
2
Pour les appels de méthode, une cible de type (id) générera un avertissement si la méthode ciblée n'a été déclarée nulle part. Ainsi, dans votre exemple, doSomethingWeirdYouveNeverHeardOf aurait dû être déclaré quelque part pour qu'il n'y ait pas d'avertissement.
bbum
Vous avez raison, un meilleur exemple serait quelque chose comme storagePolicy.
Peter N Lewis
@PeterNLewis je ne suis pas d'accord au Often you should use NSObject*lieu de id. En spécifiant, NSObject*vous dites en fait explicitement que l'objet est un NSObject. Tout appel de méthode à l'objet entraînera un avertissement, mais aucune exception d'exécution tant que cet objet répondra effectivement à l'appel de méthode. L'avertissement est évidemment ennuyeux, idc'est donc mieux. De grossier vous pouvez alors être plus précis en disant par exemple id<MKAnnotation>, ce qui dans ce cas signifie quel que soit l'objet, il doit se conformer au protocole MKAnnotation.
pnizzle
1
Si vous allez utiliser id <MKAnnotation>, vous pouvez tout aussi bien utiliser NSObject <MKAnnotation> *. Ce qui vous permet ensuite d'utiliser l'une des méthodes de MKAnnotation et l'une des méthodes de NSObject (c'est-à-dire tous les objets de la hiérarchie de classe racine NSObject normale), et d'obtenir un avertissement pour toute autre chose, ce qui est bien mieux que pas d'avertissement et un crash d'exécution.
Peter N Lewis
8

Si une méthode a un type de idretour, vous pouvez renvoyer n'importe quel objet Objective-C.

void signifie que la méthode ne retournera rien.

void *est juste un pointeur. Vous ne pourrez pas modifier le contenu de l'adresse vers laquelle pointe le pointeur.

fphilipe
la source
2
Comme cela s'applique à la valeur de retour d'une méthode, principalement à droite. Comme cela s'applique à la déclaration de variables ou d'arguments, pas tout à fait. Et vous pouvez toujours convertir un (void *) en un type plus spécifique si vous voulez lire / écrire le contenu - non pas que ce soit une bonne idée de le faire.
bbum
8

idest un pointeur vers un objet Objective-C. void *est un pointeur vers n'importe quoi . Vous pouvez utiliser à la void *place de id, mais ce n'est pas recommandé car vous n'obtiendrez jamais d'avertissement du compilateur pour quoi que ce soit.

Vous voudrez peut-être voir stackoverflow.com/questions/466777/whats-the-difference-b between - declaring-a - variable - id- and- nsobject et unixjunkie.blogspot.com/2008/03/id-vs-nsobject-vs -id.html .

Michael
la source
1
Pas assez. Les variables typées (void *) ne peuvent pas du tout être la cible des appels de méthode. Cela entraîne un "avertissement: type de récepteur non valide 'void *'" du compilateur.
bbum
@bbum: void *les variables typées peuvent très certainement être la cible des invocations de méthodes - c'est un avertissement, pas une erreur. Non seulement cela, vous pouvez le faire: int i = (int)@"Hello, string!";et le suivi avec: printf("Sending to an int: '%s'\n", [i UTF8String]);. C'est un avertissement, pas une erreur (et pas exactement recommandé, ni portable). Mais la raison pour laquelle vous pouvez faire ces choses est tout de base C.
johne
1
Désolé. Vous avez raison; c'est un avertissement, pas une erreur. Je traite simplement les avertissements comme des erreurs toujours et partout.
bbum
4
/// Represents an instance of a class.
struct objc_object {
    Class isa  OBJC_ISA_AVAILABILITY;
};

/// A pointer to an instance of a class.
typedef struct objc_object *id;

Le code ci-dessus provient de objc.h, il semble donc que id est une instance de objc_object struct et isa pointer peut se lier à n'importe quel objet Objective C Class, tandis que void * est juste un pointeur non typé.

jack
la source
2

Je crois comprendre que id représente un pointeur vers un objet tandis que void * peut vraiment pointer vers n'importe quoi, tant que vous le castez ensuite vers le type que vous souhaitez utiliser comme

hhafez
la source
Si vous effectuez un cast de (void *) vers un type d'objet, y compris id, vous avez très probablement mal fait. Il y a des raisons de le faire, mais elles sont rares, espacées et presque toujours indicatives d'un défaut de conception.
bbum
1
citation "il y a des raisons de le faire, mais elles sont peu nombreuses, très éloignées" c'est vrai. Ça dépend de la situation. Cependant, je ne ferais pas une déclaration générale comme "vous faites très probablement mal" sans un certain contexte.
hhafez
Je ferais une déclaration générale; a dû traquer et corriger trop de bugs à cause du casting vers le mauvais type avec void * entre les deux. La seule exception concerne les API basées sur le rappel qui acceptent un argument de contexte void * dont le contrat stipule que le contexte restera intact entre la configuration du rappel et la réception du rappel.
bbum
0

En plus de ce qui a déjà été dit, il existe une différence entre les objets et les pointeurs liés aux collections. Par exemple, si vous voulez mettre quelque chose dans NSArray, vous avez besoin d'un objet (de type "id"), et vous ne pouvez pas y utiliser de pointeur de données brutes (de type "void *"). Vous pouvez utiliser [NSValue valueWithPointer:rawData]pour convertir void *rawDdataau type "id" pour l'utiliser dans une collection. En général, "id" est plus flexible et a plus de sémantique liée aux objets qui lui sont attachés. Il y a plus d'exemples expliquant le type d'identifiant de l'Objectif C ici .

battlmonstr
la source