objective-c pointers

objective c - ¿Por qué[object doSomething] y no[* object doSomething]?



objective-c pointers (9)

En Objective-C, ¿por qué [object doSomething] ? ¿No sería [*object doSomething] ya que está llamando a un método en el objeto ?, lo que significa que debe desreferenciar el puntero?


  1. No es un puntero, es una referencia a un objeto.
  2. No es un método, es un mensaje.

El tiempo de ejecución de Objective-C puede necesitar revertir el objeto a un par de funciones diferentes, por lo que quiere la referencia del objeto, y no el objeto en sí.


La respuesta se remonta a las raíces C de Objective-C. Objective-C se escribió originalmente como preprocesador de compilación para C. Es decir, Objective-C no se compiló tanto como se transformó en C directa y luego se compiló.

Comience con la definición del tipo id . Se declara como:

typedef struct objc_object { Class isa; } *id;

Es decir, un id es un puntero a una estructura cuyo primer campo es del tipo Class (que, en sí mismo, es un puntero a una estructura que define una clase). Ahora, considere NSObject :

@interface NSObject <NSObject> { Class isa; }

Tenga en cuenta que el diseño de NSObject y el diseño del tipo al que apunta id son idénticos. Esto se debe a que, en realidad, una instancia de un objeto Objective-C es simplemente un puntero a una estructura cuyo primer campo, siempre un puntero, apunta a la clase que contiene los métodos para esa instancia (junto con algunos otros metadatos). )

Cuando subclasifica NSObject y agrega algunas variables de instancia, para todos los efectos, simplemente crea una nueva estructura C que contiene las variables de instancia como ranuras en esa estructura concatenada en las ranuras para las variables de instancia para todas las superclases. (El tiempo de ejecución moderno funciona de forma ligeramente diferente para que una superclase pueda tener ivars añadidos sin requerir que todas las subclases sean recompiladas).

Ahora, considere la diferencia entre estas dos variables:

NSRect foo; NSRect *bar;

(NSRect es una estructura simple de C, sin ObjC involucrado). foo se crea con el almacenamiento en la pila. No sobrevivirá una vez que el marco de pila esté cerrado, pero tampoco tendrá que liberar memoria. bar es una referencia a una estructura de NSRect que, muy probablemente, se creó en el montón utilizando malloc() .

Si intentas decir:

NSArray foo; NSArray *bar;

El compilador se quejará sobre el primero, diciendo que algo similar a los objetos basados ​​en pila no está permitido en Objective-C . En otras palabras, todos los objetos Objective-C se deben asignar desde el montón (más o menos-- hay una o dos excepciones, pero son comparativamente esotéricas para esta discusión) y, como resultado, siempre se refiere a un objeto a través de la dirección de dicho objeto en el montón; siempre está trabajando con punteros a objetos (y el tipo de id realmente es solo un puntero a cualquier objeto antiguo).

Volviendo a las raíces del preprocesador C del lenguaje, puede traducir cada llamada de método a una línea equivalente de C. Por ejemplo, las siguientes dos líneas de código son idénticas:

[myArray objectAtIndex: 42]; objc_msgSend(myArray, @selector(objectAtIndex:), 42);

Del mismo modo, un método declarado así:

- (id) objectAtIndex: (NSUInteger) a;

Es equivalente a la función C declarada así:

id object_at_index(id self, SEL _cmd, NSUInteger a);

Y, mirando a objc_msgSend() , el primer argumento se declara de tipo id :

OBJC_EXPORT id objc_msgSend(id self, SEL op, ...);

Y esa es exactamente la razón por la que no usa *foo como objetivo de una llamada a un método. Realice la traducción a través de los formularios anteriores: la llamada a [myArray objectAtIndex: 42] se traduce a la llamada de función C anterior que luego debe invocar algo con la declaración de llamada de función C equivalente (todo distorsionado en la sintaxis del método).

La referencia del objeto se lleva a cabo porque le da al messenger - objc_msgSend () acceso a la clase para luego encontrar la implementación del método - así como también esa referencia que se convierte en el primer parámetro - el auto - del método que es eventualmente ejecutado.

Si realmente quieres profundizar, comienza aquí . Pero no se moleste hasta que haya asimilado esto por completo .


Nunca desreferencia puntos de objeto, punto. El hecho de que se tipean como punteros en lugar de solo "tipos de objetos" es un artefacto del patrimonio C de la lengua. Es exactamente equivalente al sistema de tipos de Java, donde a los objetos siempre se accede a través de referencias. Nunca se desreferencia un objeto en Java; de hecho, no se puede. No deberías pensar en ellos como indicadores, porque semánticamente, no lo son. Solo son referencias de objeto.


Parte de la razón es que obtendría excepciones de puntero nulo a izquierda y derecha. Enviar un mensaje a nil está permitido y, a menudo, perfectamente legítimo (no hace nada y no genera un error).

Pero puedes pensar que es análogo a la notación de C ++: Ejecuta el método y elimina el puntero en una pieza de azúcar sintáctica.


Porque objc_msgSend() se declara así:

id objc_msgSend(id theReceiver, SEL theSelector, ...)


Un objeto en Objective-C es esencialmente una struct . El primer miembro de la struct es la Class isa (el tamaño total de la struct se puede determinar usando isa ). Los miembros subsiguientes de la struct pueden incluir variables de instancia.

Cuando declara un objeto Objective-C, siempre lo declara como un tipo de puntero porque el tiempo de ejecución le otorga a su objeto otros métodos y funciones; si estos cambian algún miembro de la struct (modificando variables de instancia, etc.) se "aplicarán" a todas las referencias a su objeto, y no solo localmente dentro del método o función.


Yo diría de esta manera: lo que un lenguaje asocia a una serie de alfabetos es solo una convención. Las personas que diseñaron Objective-C decidieron que

[x doSomething];

para significar "enviar el mensaje doSomething al objeto señalado por x". Lo definieron de esa manera, sigues la regla :) Una peculiaridad de Objective-C, en comparación con, por ejemplo, C ++, es que no tiene una sintaxis para contener un objeto en sí mismo, no un puntero a objeto. Asi que,

NSString* string;

está bien, pero

NSString string;

es ilegal. Si esto último fuera posible, tendría que haber una forma de "enviar el mensaje capitalizedString a una cadena de caracteres", no a "enviar el mensaje capitalizedString a una cadena apuntada por una string ". Pero en realidad, siempre envía un mensaje a un objeto señalado por una variable en su código fuente.

Entonces, si los diseñadores de Objective-C hubieran seguido tu lógica, tendrías que escribir

[*x doSomething];

cada vez que envía un mensaje ... Verá, * debe aparecer siempre después del corchete inicial [ , formando la combinación [* . En ese momento, creo que acepta que es mejor rediseñar el idioma para que solo tenga que escribir [ lugar de [* , cambiando el significado de la secuencia de letras [x doSomething] .


Realmente no deberías pensar en estos como punteros a objetos. Es una especie de detalle histórico de implementación que son punteros, y que los usa así en la sintaxis de envío de mensajes (vea la respuesta de @ bbum). De hecho, son solo "identificadores de objetos" (o referencias). Retrocedamos un poco para ver el razonamiento conceptual.

Objective-C fue propuesto y discutido por primera vez en este libro: Programación orientada a objetos: un enfoque evolutivo . No es inmensamente práctico para los programadores modernos de Cocoa, pero las motivaciones para el lenguaje están ahí.

Tenga en cuenta que en el libro todos los objetos reciben el tipo de id . Usted no ve en absoluto los Object * más específicos en el libro; esos son solo una fuga en la abstracción cuando hablamos del "por qué". Esto es lo que dice el libro:

Los identificadores de objetos deben identificar de forma única tantos objetos como puedan coexistir en el sistema en cualquier momento. Se almacenan en variables locales, pasadas como argumentos en expresiones de mensaje y en llamadas a función, contenidas en variables de instancia (campos dentro de objetos) y en otros tipos de estructuras de memoria. En otras palabras, se pueden usar de manera tan fluida como los tipos incorporados del lenguaje base.

La forma en que un identificador de objeto realmente identifica el objeto es un detalle de implementación para el que muchas opciones son verosímiles. Una elección razonable, sin duda una de las más simples, y la que se usa en Objective-C, es usar la dirección física del objeto en la memoria como su identificador. Objective-C da a conocer esta decisión a C generando una instrucción typedef en cada archivo. Esto define un nuevo tipo, id, en términos de otro tipo que C ya entiende, a saber, punteros a estructuras. [...]

Una identificación consume una cantidad fija de espacio. [...] Este espacio no es lo mismo que el espacio ocupado por los datos privados en el objeto mismo.

(pp58-59, 2da ed)

Entonces la respuesta a tu pregunta es doble:

  1. El diseño del lenguaje especifica que el identificador de un objeto no es lo mismo que un objeto en sí mismo, y el identificador es el objeto al que envía los mensajes, no el objeto en sí.
  2. El diseño no dicta, pero sugiere, la implementación que tenemos ahora, donde los punteros a los objetos se usan como identificadores.

La sintaxis estrictamente tipada donde dice "un objeto específicamente de tipo NSString" y, por lo tanto, utiliza NSString * es un cambio más moderno, y es básicamente una opción de implementación, equivalente a id .

Si esto parece una respuesta sensata a una pregunta sobre desreferenciación de punteros, es importante tener en cuenta que los objetos en Objective-C son "especiales" según la definición del lenguaje. Se implementan como estructuras y se pasan como punteros a las estructuras, pero son conceptualmente diferentes.