Al empezar a estudiar Objective-C, es normal detenerse ante el tipo id.
Surge la duda: «¿Qué tipo es este? ¿Por qué compila aunque introduzca cualquier objeto?»
En resumen, id es un «puntero universal que puede apuntar a cualquier objeto» y sustenta el tipado dinámico y el duck typing de Objective-C.
En este artículo veremos qué es exactamente id, cómo funcionan el tipado dinámico y el duck typing en código real, y cuándo conviene usarlos o tratarlos con cautela.
¿Qué es exactamente el tipo id?
En una palabra, id es un «puntero a cualquier objeto de Objective-C».
Puedes almacenar cualquier objeto sin especificar una clase concreta como NSString * o NSArray *.
En realidad, id es más sencillo de lo que parece. Internamente se define así.
// objc.h Definición interna (resumen)
typedef struct objc_object {
Class isa; // Puntero que indica a qué clase pertenece este objeto
} *id;
Es decir, id es un puntero a una estructura que contiene un único puntero, isa.
Este isa actúa como una etiqueta que informa en tiempo de ejecución: «Soy una instancia de esta clase».
Un detalle interesante: id ya es un puntero, por lo que se escribe id obj, sin añadir un asterisco como en id *obj.
Es fácil confundirlo con NSString *str.
Así funciona el tipado dinámico
El tipado dinámico significa que «el tipo real de un objeto se determina en tiempo de ejecución, no de compilación».
La clase real del objeto almacenado en id se determina durante la ejecución observando el puntero isa.
Por eso es posible escribir código como este.
id obj = @"Hello"; // Ahora es NSString
NSLog(@"%@", [obj class]); // Salida: __NSCFConstantString
obj = @[@1, @2, @3]; // Ahora cambia a NSArray
NSLog(@"%@", [obj class]); // Salida: __NSArrayI
La misma variable, obj, pasa de ser una cadena a un array.
El compilador no lo impide porque deja para el tiempo de ejecución la determinación del tipo real.
Lo mismo ocurre al enviar mensajes. Al llamar a [obj length], el compilador no puede saber si obj responderá a length.
En ese momento, el runtime inspecciona la clase de obj, busca el método y lo ejecuta. Esto se llama despacho de mensajes.
La comparación es la siguiente.
- Tipado estático: el compilador fija y comprueba los tipos por adelantado
- Tipado dinámico: se observa el objeto real durante la ejecución y se decide
En Objective-C,
idy el tipado dinámico reflejan la filosofía de «introducirlo primero y preguntar qué es realmente al ejecutarlo».
¿Cuál es el verdadero significado del duck typing?
Aquí se produce un malentendido frecuente: reducir el duck typing a «no comprobar tipos».
La expresión original del duck typing es:
«Si camina como un pato y grazna como un pato, es un pato».
Lo importante no es a qué clase pertenece el objeto, sino «si puede responder a ese mensaje».
En Objective-C puedes comprobarlo directamente con respondsToSelector:.
// Sin importar la clase, llama al método si responde a él
if ([obj respondsToSelector:@selector(quack)]) {
[obj quack]; // Si puede graznar como un pato, trátalo como un pato
}
No importa si obj pertenece a la clase Duck o a la clase Robot.
Si responde al mensaje quack, para nuestro código es un «pato».
Esto difiere del polimorfismo basado en herencia.
No necesitan heredar de la misma clase padre: incluso clases sin relación pueden tratarse igual si tienen el mismo nombre de método.
Esta flexibilidad sustenta diseños fundamentales de Cocoa como el patrón delegado y target-action.
El id, tan cómodo como peligroso: ¿cuándo hay que tener cuidado?
id tiene un precio a cambio de su libertad.
Como el compilador apenas comprueba los tipos, enviar un mensaje no compatible supera la compilación, pero la aplicación se bloquea durante la ejecución.
Es el famoso bloqueo unrecognized selector sent to instance.
Por eso, en la práctica se usa así.
- Si el tipo es seguro, especifica una clase concreta, como
NSString *, en lugar deid - Si debes usar
id, protégelo antes de llamar conrespondsToSelector: - Define un protocolo delegado para documentar «responde a este mensaje».
Hoy, la introducción de instancetype y genéricos como NSArray<NSString *> * ha reducido mucho el uso indiscriminado de id.
Aun así, id sigue latiendo como un corazón en el código de bajo nivel de los frameworks y del runtime.
En resumen, id simboliza la naturaleza dinámica de Objective-C, y el tipado dinámico y el duck typing comparten la idea de «juzgar los objetos por su comportamiento, no por su clase».
Al principio puede parecer extraño y peligroso, pero al entender el principio, el diseño de los frameworks de Cocoa resulta mucho más claro.
Espero que id, que hoy te tenía atascado, resulte ahora un poco menos intimidante. ¡Disfruta aprendiendo Objective-C!

