2008-10-01 15 views
101

Como desarrollador de Java que está leyendo la documentación de Objective-C 2.0 de Apple: me pregunto qué significa "enviando un mensaje a cero", y mucho menos cómo es realmente útil. Tomando un extracto de la documentación:Enviando un mensaje a cero en Objective-C

Hay varios patrones en Cocoa que aprovechan este hecho. El valor de regresar de un mensaje a nil también puede ser válido:

  • Si el método devuelve un objeto, cualquier tipo de puntero, cualquier escalar número entero de tamaño inferior o igual a sizeof (void *), un flotador, un doble, un doble larga, o un largo tiempo, entonces un mensaje enviado a devuelve nil 0.
  • Si el método devuelve una estructura, según la definición de la función de Mac OS X ABI Guía llamada a ser devuelto en registros, luego un mensaje enviado a cero devuelve 0.0 para cada campo en el estructura de datos. Otros tipos de datos struct no se rellenarán con ceros.
  • Si el método devuelve algo que no sea el valor mencionado escribe que el valor de retorno de un mensaje enviado a cero no está definido.

¿Ha dejado Java mi cerebro incapaz de asimilar la explicación anterior? ¿O hay algo que me falta que lo haría tan claro como el vidrio?

Tengo la idea de mensajes/receptores en Objective-C, simplemente estoy confundido acerca de un receptor que resulta ser nil.

+2

¡También tenía un fondo de Java y estaba aterrorizado por esta característica agradable al principio, pero ahora la encuentro absolutamente ENCANTADORA !; –

+1

Gracias, esa es una gran pregunta. ¿Has visto para ver los beneficios de eso? Me parece algo "no un error, una característica". Sigo recibiendo errores donde Java simplemente me daría una bofetada con una excepción, así que sabía dónde estaba el problema.No estoy contento de cambiar la excepción del puntero nulo para guardar una o dos líneas de código triviales aquí y allá. –

Respuesta

89

Bueno, creo que se puede describir con un ejemplo muy artificial. Digamos que usted tiene un método en Java que imprime todos los elementos en un ArrayList:

void foo(ArrayList list) 
{ 
    for(int i = 0; i < list.size(); ++i){ 
     System.out.println(list.get(i).toString()); 
    } 
} 

Ahora, si se llama a este método, así: someObject.foo (NULL); Probablemente obtendrá una NullPointerException cuando intente acceder a la lista, en este caso en la llamada a list.size(); Ahora, probablemente nunca llamaría a someObject.foo (NULL) con el valor NULL así. Sin embargo, es posible que haya obtenido ArrayList de un método que devuelve NULL si se encuentra con algún error al generar ArrayList como algúnObject.foo (otherObject).getArrayList());

Por supuesto, usted también tendrá problemas si haces algo como esto:

ArrayList list = NULL; 
list.size(); 

Ahora, en Objective-C, tenemos el método equivalente:

- (void)foo:(NSArray*)anArray 
{ 
    int i; 
    for(i = 0; i < [anArray count]; ++i){ 
     NSLog(@"%@", [[anArray objectAtIndex:i] stringValue]; 
    } 
} 

Ahora, si tenemos el siguiente código:

[someObject foo:nil]; 

tenemos la misma situación en la que Java producirá una NullPointerException. Se accederá primero al objeto nil en [un contador de Array] Sin embargo, en lugar de lanzar una NullPointerException, Objective-C simplemente devolverá 0 de acuerdo con las reglas anteriores, por lo que el bucle no se ejecutará. Sin embargo, si configuramos el ciclo para ejecutar un número determinado de veces, entonces primero estamos enviando un mensaje a un Array en [anArray objectAtIndex: i]; Esto también devolverá 0, pero dado que objectAtIndex: devuelve un puntero, y un puntero a 0 es nil/NULL, NSLog se pasará nulo cada vez a través del ciclo. (Aunque NSLog es una función y no un método, se imprime (nulo) si pasa un NSString nulo.

En algunos casos es más agradable tener una NullPointerException, ya que puede saber de inmediato que algo está mal con el programa , pero a menos que detecte la excepción, el programa fallará. (En C, intentar desreferenciar a NULL de esta manera hace que el programa se cuelgue). En Objective-C, en cambio, solo causa un comportamiento de tiempo de ejecución posiblemente incorrecto. tiene un método que no se rompe si devuelve 0/nil/NULL/a zeroed struct, esto le evita tener que verificar para asegurarse de que el objeto o los parámetros son nulos.

+32

Probablemente valga la pena mencionar que este comportamiento ha sido objeto de gran debate en la comunidad Objective-C en las últimas dos décadas. La compensación entre "seguridad" y "comodidad" se evalúa de manera diferente por diferentes personas. –

+3

En la práctica, existe una gran cantidad de simetría entre pasar mensajes a cero y cómo funciona Objective-C, especialmente en la nueva función de punteros débiles en ARC. Los punteros débiles se ponen a cero automáticamente. Así que diseña tu API para que pueda responder a 0/nil/NIL/NULL etc. – Cthutu

+1

Creo que si haces 'myObject-> iVar' se bloqueará, independientemente de que sea C _with_ o _with_ objetos. (Lo siento por gravedig.) – 11684

16

Lo que significa es que el tiempo de ejecución no produce un error cuando se llama a objc_msgSend en el puntero nulo; en su lugar, devuelve un valor (a menudo útil). Los mensajes que pueden tener un efecto secundario no hacen nada.

Es útil porque la mayoría de los valores predeterminados son más apropiados que un error. Por ejemplo:

[someNullNSArrayReference count] => 0 

Es decir, nil parece ser la matriz vacía. Ocultar una nula referencia NSView no hace nada. Práctico, ¿eh?

9

Significa que a menudo no tiene que Verifique que no haya objetos en todas partes por seguridad, particularmente:

[someVariable release]; 

o, como se ha señalado, varios métodos de recuento y la longitud de todo el retorno 0 cuando se tiene un valor nulo, por lo que no tiene que agregar comprobaciones adicionales para nulo todo:

if ([myString length] > 0) 

o esto:

return [myArray count]; // say for number of rows in a table 
+0

Tenga en cuenta que la otra cara de la moneda es el potencial de errores como "if ([myString length] == 1)" – hatfinch

+0

¿Cómo es eso un error? [myString length] devuelve cero (nil) si myString es nulo ... una cosa que creo que podría ser un problema es [myView frame], que creo que puede darte algo raro si myView es nulo. –

+0

Si diseña sus clases y métodos en torno al concepto de que los valores predeterminados (0, nil, NO) significan "no útiles", esta es una herramienta poderosa. Nunca tengo que comprobar mis cadenas por cero antes de verificar la longitud. Para mí, la cadena vacía es inútil y una cadena nula cuando estoy procesando texto. También soy desarrollador de Java y sé que los puristas de Java evitarán esto, pero ahorra mucha codificación. –

6

No crea que "the receiver being nil"; Estoy de acuerdo, que es bastante raro. Si está enviando un mensaje a cero, no hay receptor. Simplemente estás enviando un mensaje a nada.

Cómo lidiar con eso es una diferencia filosófica entre Java y Objective-C: en Java, eso es un error; en Objective-C, es un no-op.

+0

Existe una excepción a ese comportamiento en Java, si llama a una función estática con un valor nulo, es equivalente a invocar la función en la clase de tiempo de compilación de la variable (no importa si es nula). –

12

En la cita de la documentación, hay dos conceptos separados - quizá sería mejor si la documentación que hizo más claro:

Hay varios patrones en Cocoa que se aprovechan de este hecho.

El valor devuelto por un mensaje a cero también puede ser válida:

el primero es probablemente más relevante aquí: suelen ser capaz de enviar mensajes a nil hace que el código sea más sencillo - que no tiene para verificar valores nulos en todas partes.El ejemplo clásico es probablemente el método de acceso:

- (void)setValue:(MyClass *)newValue { 
    if (value != newValue) { 
     [value release]; 
     value = [newValue retain]; 
    } 
} 

Si el envío de mensajes a nil no fuera válida, este método sería más compleja - que tendría que tener dos controles adicionales para asegurar value y newValue no son nil antes de enviarles mensajes

El último punto (que los valores devueltos de un mensaje a nil también son típicamente válidos), sin embargo, agrega un efecto multiplicador al primero. Por ejemplo:

if ([myArray count] > 0) { 
    // do something... 
} 

Este código de nuevo no requiere un cheque por nil valores, y fluye de forma natural ...

dicho todo esto, la flexibilidad adicional que ser capaz de enviar mensajes a nil viene a un costo Existe la posibilidad de que en algún momento escriba un código que falla de una manera peculiar porque no tuvo en cuenta la posibilidad de que un valor sea nil.

50

Un mensaje a nil no hace nada y devuelve nil, Nil, NULL, 0 o 0.0.

38

Todas las otras publicaciones son correctas, pero tal vez sea el concepto lo que importa aquí.

En las llamadas al método Objective-C, cualquier referencia de objeto que pueda aceptar un selector es un destino válido para ese selector.

Esto ahorra MUCHO "¿es el objeto de destino del tipo X?" código: siempre que el objeto receptor implemente el selector, hace absolutamente ninguna diferencia ¡de qué clase es! nil es un objeto NSO que acepta cualquier selector; simplemente no hace nada. Esto también elimina una gran cantidad de código de "verificación de nulo, no envíe el mensaje si es verdadero". (El concepto "si lo acepta, lo implementa" también es lo que le permite crear los protocolos , que son como las interfaces Java: una declaración de que si una clase implementa los métodos establecidos, entonces se ajusta al protocolo.)

La razón para esto es eliminar el código de mono que no hace nada excepto mantener feliz al compilador. Sí, obtiene la sobrecarga de una llamada a otro método más, pero ahorra tiempo de programador, que es un recurso mucho más costoso que el tiempo de CPU. Además, está eliminando más código y más complejidad condicional de su aplicación.

Aclarar para downvoters: usted puede pensar que esto no es un buen camino a seguir, pero es cómo se implementa el lenguaje, y es el lenguaje de programación recomendada en Objective-C (ver las conferencias de programación de iPhone de Stanford).

6

Los mensajes ObjC que se envían a cero y cuyos valores de retorno tienen un tamaño mayor que sizeof (void *) producen valores indefinidos en los procesadores PowerPC. Además de eso, estos mensajes provocan que se devuelvan valores indefinidos en los campos de estructuras cuyo tamaño es mayor que 8 bytes en los procesadores Intel también.Vincent Gable ha descrito esto muy bien en su blog post

6

No creo que ninguna de las otras respuestas lo haya mencionado claramente: si estás acostumbrado a Java, debes tener en cuenta que, si bien Objective-C en Mac OS X tiene soporte para manejo de excepciones, es una función de idioma opcional que se puede activar/desactivar con un indicador de compilación. Supongo que este diseño de "enviar mensajes al nil es seguro" es anterior a la inclusión del soporte de manejo de excepciones en el lenguaje y se hizo con un objetivo similar en mente: los métodos pueden devolver nil para indicar errores y desde enviar un mensaje al nil generalmente devuelve nil, lo que permite que la indicación de error se propague a través de su código para que no tenga que verificarlo en cada mensaje. Solo debes verificarlo en los puntos donde sea importante. Personalmente creo que la propagación de excepciones & es una mejor manera de abordar este objetivo, pero no todos pueden estar de acuerdo con eso. (Por otro lado, por ejemplo, no me gusta el requisito de Java para que tenga que declarar qué excepciones puede arrojar un método, que a menudo lo obliga sintácticamente propagar declaraciones de excepción en todo el código, pero esa es otra discusión)

He publicado una respuesta similar, pero más larga, a la pregunta relacionada "Is asserting that every object creation succeeded necessary in Objective C?" si desea más detalles.

+0

Nunca pensé en eso de esa manera. Esto parece ser una característica muy conveniente. – mk12

+2

Buena conjetura, pero históricamente inexacta sobre por qué se tomó la decisión. El manejo de excepciones existía en el lenguaje desde el principio, aunque los manejadores de excepciones originales eran bastante primitivos en comparación con el idioma moderno. Nil-come-mensaje fue una elección de diseño consciente derivado del comportamiento * opcional * del objeto Nil en Smalltalk. Cuando se diseñaron las API NeXTSTEP originales, el encadenamiento de métodos fue bastante común y se usó un retorno 'nil' para cortocircuitar una cadena en un NO-op. – bbum

11

De Greg Parker 's site:

Si se ejecuta LLVM compilador 3,0 (Xcode 4.2) o posterior

 
Messages to nil with return type | return 
Integers up to 64 bits   | 0 
Floating-point up to long double | 0.0 
Pointers       | nil 
Structs       | {0} 
Any _Complex type    | {0, 0} 
2

C representa nada como 0 para valores primitivos, y NULL para los punteros (que es equivalente a 0 en un contexto de puntero).

Objective-C se basa en la representación de C de nada mediante la adición de nil. nil es un puntero de objeto a nada. Aunque semánticamente distinto de NULL, son técnicamente equivalentes entre sí.

Los NSObjects recién asignados comienzan la vida con sus contenidos establecidos en 0. Esto significa que todos los punteros que objetan tienen otros objetos como nulos, por lo que no es necesario, por ejemplo, establecer self. (Asociación) = nil en los métodos init.

El comportamiento más notable de nada es que puede recibir mensajes.

En otros idiomas, como C++ (o Java), esto bloquearía su programa, pero en Objective-C, invocar un método en nil devuelve un valor cero. Esto simplifica enormemente expresiones, ya que evita la necesidad de comprobar la nula antes de hacer nada:

// For example, this expression... 
if (name != nil && [name isEqualToString:@"Steve"]) { ... } 

// ...can be simplified to: 
if ([name isEqualToString:@"Steve"]) { ... } 

Ser consciente de cómo nula obras en Objective-C permite que esta comodidad a ser una característica, y no un error al acecho en su solicitud. Asegúrese de protegerse de los casos en que los valores nulos no son deseados, ya sea verificando y regresando temprano para fallar silenciosamente, o agregando un NSParameterAssert para arrojar una excepción.

Fuente: http://nshipster.com/nil/ https://developer.apple.com/library/ios/#documentation/cocoa/conceptual/objectivec/Chapters/ocObjectsClasses.html (enviando el mensaje a cero).

Cuestiones relacionadas