Objetivo-C: diferencia entre id y void *

Respuestas:

240

void * significa "una referencia a un poco de memoria aleatoria con contenido sin tipo / desconocido"

id significa "una referencia a algún objeto Objective-C aleatorio de clase desconocida"

Hay diferencias semánticas adicionales:

  • En los modos GC Only o GC Supported, el compilador emitirá barreras de escritura para referencias de tipo id, pero no para tipo void *. Al declarar estructuras, esto puede ser una diferencia crítica. Declarar iVars como void *_superPrivateDoNotTouch;causará la cosecha prematura de objetos si en _superPrivateDoNotTouchrealidad es un objeto. No hagas eso.

  • intentar invocar un método en una referencia de void *tipo generará una advertencia de compilación.

  • intentar invocar un método en un idtipo solo advertirá si el método al que se llama no se ha declarado en ninguna de las @interfacedeclaraciones vistas por el compilador.

Por lo tanto, uno nunca debe referirse a un objeto como a void *. Del mismo modo, uno debe evitar el uso de una idvariable escrita para referirse a un objeto. Utilice la referencia de tipo de clase más específica que pueda. Incluso NSObject *es mejor que idporque el compilador puede, al menos, proporcionar una mejor validación de las invocaciones de métodos contra esa referencia.

El uso común y válido de void *es como una referencia de datos opaca que se pasa a través de alguna otra API.

Considere el sortedArrayUsingFunction: context:método de NSArray:

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

La función de clasificación se declararía como:

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

En este caso, el NSArray simplemente pasa todo lo que pasa como contextargumento al método a través del contextargumento. Es un fragmento opaco de datos del tamaño de un puntero, en lo que respecta a NSArray, y puede usarlo para el propósito que desee.

Sin una característica de tipo de cierre en el idioma, esta es la única forma de llevar un trozo de datos con una función. Ejemplo; si desea que mySortFunc () clasifique condicionalmente como mayúsculas o minúsculas, sin dejar de ser seguro para subprocesos, debería pasar el indicador de mayúsculas y minúsculas en el contexto, probablemente arrojando al entrar y salir.

Frágil y propenso a errores, pero la única forma.

Los bloques resuelven esto: los bloques son cierres para C. Están disponibles en Clang - http://llvm.org/ y son penetrantes en Snow Leopard ( http://developer.apple.com/library/ios/documentation/Performance /Reference/GCD_libdispatch_Ref/GCD_libdispatch_Ref.pdf ).

bbum
fuente
Además, estoy bastante seguro de que idse supone que responde a -retainy -release, mientras que a void*es completamente opaco para la persona que llama. No puede pasar un puntero arbitrario a -performSelector:withObject:afterDelay:(retiene el objeto), y no puede asumir que +[UIView beginAnimations:context:]retendrá el contexto (el delegado de animación debe mantener la propiedad del contexto; UIKit retiene el delegado de animación).
tc.
3
No se pueden hacer suposiciones sobre lo que idresponde. Un idpuede referirse fácilmente a una instancia de una clase que no es inherente a NSObject. Sin embargo, en términos prácticos, su declaración coincide mejor con el comportamiento del mundo real; no puede mezclar <NSObject>clases que no se implementen con Foundation API y llegar muy lejos, ¡eso definitivamente es seguro!
bbum
2
Ahora idy los Classtipos se tratan como puntero de objeto retenible en ARC. Entonces, la suposición es cierta al menos bajo ARC.
eonil
¿Qué es "cosecha prematura"?
Brad Thomas
1
@BradThomas Cuando un recolector de basura recolecta memoria antes de que el programa termine con ella.
bbum
21

id es un puntero a un objeto C objetivo, donde como void * es un puntero a cualquier cosa.

id también desactiva las advertencias relacionadas con la llamada a métodos desconocidos, por ejemplo:

[(id)obj doSomethingWeirdYouveNeverHeardOf];

no dará la advertencia habitual sobre métodos desconocidos. Por supuesto, generará una excepción en tiempo de ejecución a menos que obj sea nulo o realmente implemente ese método.

A menudo debe usar, NSObject*o lo id<NSObject>prefiere id, lo que al menos confirma que el objeto devuelto es un objeto Cocoa, por lo que puede usar métodos seguros como retención / liberación / liberación automática en él.

Peter N Lewis
fuente
2
Para las invocaciones de métodos, un objetivo de tipo (id) generará una advertencia si el método de destino no se ha declarado en ninguna parte. Por lo tanto, en su ejemplo, doSomethingWeirdYouveNeverHeardOf habría tenido que haber sido declarado en algún lugar para que no hubiera una advertencia.
bbum
Tienes toda la razón, un mejor ejemplo sería algo como storagePolicy.
Peter N Lewis
@PeterNLewis No estoy de acuerdo en Often you should use NSObject*lugar de id. Al especificar NSObject*, en realidad está diciendo explícitamente que el objeto es un NSObject. Cualquier llamada de método al objeto dará como resultado una advertencia, pero no habrá una excepción de tiempo de ejecución mientras ese objeto realmente responda a la llamada de método. La advertencia es obviamente molesta, así que ides mejor. Por lo general, puede ser más específico, por ejemplo id<MKAnnotation>, diciendo que, en este caso, sea cual sea el objeto, debe cumplir con el protocolo MKAnnotation.
pnizzle
1
Si vas a usar id <MKAnnotation>, entonces también puedes usar NSObject <MKAnnotation> *. Lo que luego le permite usar cualquiera de los métodos en MKAnnotation y cualquiera de los métodos en NSObject (es decir, todos los objetos en la jerarquía de clase de raíz NSObject normal), y obtener una advertencia para cualquier otra cosa, que es mucho mejor que ninguna advertencia y un accidente de tiempo de ejecución.
Peter N Lewis
8

Si un método tiene un tipo de idretorno, puede devolver cualquier objeto Objective-C.

void significa que el método no devolverá nada.

void *Es solo un puntero. No podrá editar el contenido en la dirección a la que apunta el puntero.

fphilipe
fuente
2
Como se aplica al valor de retorno de un método, principalmente correcto. Como se aplica a declarar variables o argumentos, no del todo. Y siempre puede emitir un (nulo *) a un tipo más específico si desea leer / escribir el contenido, no es una buena idea hacerlo.
bbum
8

ides un puntero a un objeto Objective-C. void *es un puntero a cualquier cosa . Podría usar en void *lugar de id, pero no se recomienda porque nunca obtendría advertencias del compilador para nada.

Es posible que desee ver stackoverflow.com/questions/466777/whats-the-difference-between-declaring-a-variable-id-and-nsobject y unixjunkie.blogspot.com/2008/03/id-vs-nsobject-vs -id.html .

Miguel
fuente
1
No exactamente. (void *) las variables escritas no pueden ser el objetivo de las invocaciones de métodos. Resulta en "advertencia: tipo de receptor inválido 'void *'" del compilador.
bbum
@bbum: las void *variables escritas definitivamente pueden ser el objetivo de las invocaciones de métodos, es una advertencia, no un error. No sólo eso, usted puede hacer esto: int i = (int)@"Hello, string!";y el seguimiento con: printf("Sending to an int: '%s'\n", [i UTF8String]);. Es una advertencia, no un error (y no es exactamente recomendado, ni portátil). Pero la razón por la que puedes hacer estas cosas es todo básico C.
johne
1
Lo siento. Estás en lo correcto; Es una advertencia, no un error. Solo trato las advertencias como errores siempre y en todas partes.
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;

El código anterior es de objc.h, por lo que parece que id es una instancia de objc_object struct e isa pointer puede vincularse con cualquier objeto Objective C Class, mientras que void * es solo un puntero sin tipo.

Jack
fuente
2

Entiendo que id representa un puntero a un objeto, mientras que void * puede apuntar a cualquier cosa realmente, siempre y cuando lo conviertas en el tipo que deseas usar como

hhafez
fuente
Si está transmitiendo desde (void *) a algún tipo de objeto, incluida la identificación, es muy probable que lo esté haciendo mal. Hay razones para hacerlo, pero son pocas, distantes entre sí y casi siempre indican un defecto de diseño.
bbum
1
cita "hay razones para hacerlo, pero son pocas, distantes entre sí". Depende de la situación. Sin embargo, no haría una declaración general como "es muy probable que lo estés haciendo mal" sin algún contexto.
hhafez
Haría una declaración general; Tuve que cazar y corregir demasiados errores debido a la conversión al tipo incorrecto con vacío * en el medio. La única excepción son las API basadas en devolución de llamada que toman un argumento de contexto nulo * cuyo contrato establece que el contexto permanecerá intacto entre la configuración de la devolución de llamada y la recepción de la devolución de llamada.
bbum
0

Además de lo que ya se dijo, hay una diferencia entre los objetos y los punteros relacionados con las colecciones. Por ejemplo, si desea poner algo en NSArray, necesita un objeto (del tipo "id"), y no puede usar un puntero de datos sin procesar allí (del tipo "void *"). Puede usar [NSValue valueWithPointer:rawData]para convertir void *rawDdataal tipo "id" para usarlo dentro de una colección. En general, "id" es más flexible y tiene más semántica relacionada con los objetos adjuntos. Hay más ejemplos que explican el tipo de identificación del Objetivo C aquí .

battlmonstr
fuente