Enviar un mensaje a cero en Objective-C

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

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

  • Si el método devuelve un objeto, cualquier tipo de puntero, cualquier entero escalar de tamaño inferior o igual a sizeof (void *), un float, un double, un long double o un long long, entonces un mensaje enviado a nil devuelve 0 .
  • Si el método devuelve una estructura, tal como se define en la Guía de funciones de la función ABI de Mac OS X, para que se devuelva en los registros, un mensaje enviado a cero devuelve 0.0 para cada campo en la estructura de datos. Otros tipos de datos struct no se llenarán con ceros.
  • Si el método devuelve algo distinto a los tipos de valores antes mencionados, el valor de retorno de un mensaje enviado a cero no está definido.

¿Java ha dejado a 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 .

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

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

Ahora, si llama a ese método de la siguiente manera: 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 que genere ArrayList como algúnObject.foo (otherObject.getArrayList ());

Por supuesto, también tendrás 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, 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 anda mal con el progtwig, pero a menos que capte la excepción, el progtwig fallará. (En C, intentar desreferenciar a NULL de esta forma hace que el progtwig falle). En Objective-C, en cambio, solo causa un comportamiento de tiempo de ejecución posiblemente incorrecto. Sin embargo, si 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.

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

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 de tipo X?” código: ¡mientras el objeto receptor implemente el selector, no hay ninguna diferencia en qué clase es! nil es un NSObject 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 protocolos , que son como las interfaces de Java: una statement 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 mono que no hace nada excepto mantener feliz al comstackdor. Sí, obtienes la sobrecarga de otra llamada a método, pero ahorras tiempo al progtwigdor , 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.

Aclaración para downvoters: puede pensar que este no es un buen camino a seguir, pero es cómo se implementa el lenguaje, y es la expresión de progtwigción recomendada en Objective-C (consulte las conferencias de progtwigción de Stanford para iPhone).

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?

En la cita de la documentación, hay dos conceptos separados: tal vez sería mejor si la documentación lo hiciera más claro:

Hay varios patrones en Cocoa que aprovechan este hecho.

El valor devuelto por un mensaje a nil también puede ser válido:

El primero es probablemente más relevante aquí: normalmente ser capaz de enviar mensajes a nil hace que el código sea más sencillo: no tiene que verificar valores nulos en todas partes. El ejemplo canónico 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álido, este método sería más complejo; habría que tener dos controles adicionales para garantizar que 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 nuevamente no requiere una verificación de valores nil , y fluye naturalmente …

Dicho todo esto, la flexibilidad adicional de poder enviar mensajes a nil tiene algún 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 .

Desde el sitio de Greg Parker :

Si ejecuta LLVM Compiler 3.0 (Xcode 4.2) o posterior

 Mensajes a cero con tipo de retorno |  regreso
 Enteros de hasta 64 bits |  0
 Punto flotante hasta doble largo |  0.0
 Punteros |  nulo
 Estructura |  {0}
 Cualquier tipo _Complex |  {0, 0}

Significa a menudo no tener que verificar la seguridad de objetos nulos en todas partes, particularmente:

 [someVariable release]; 

o, como se indicó, varios métodos de recuento y longitud devuelven 0 cuando tiene un valor nulo, por lo que no tiene que agregar comprobaciones adicionales para nada en todas partes:

 if ( [myString length] > 0 ) 

o esto:

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

No pienses en “el receptor es nulo”; Estoy de acuerdo, eso es bastante extraño. 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.

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 lo describió muy bien en su publicación de blog

No creo que ninguna de las otras respuestas lo haya mencionado claramente: si está acostumbrado a Java, debe tener en cuenta que aunque Objective-C en Mac OS X tiene soporte para manejo de excepciones, es una función de idioma opcional que puede ser encendido / apagado con un indicador de comstackción. Supongo que este diseño de “enviar mensajes a nil es seguro” es anterior a la inclusión del soporte de manejo de excepciones en el lenguaje y se hizo con un objective similar en mente: los métodos pueden devolver nil para indicar errores y desde enviar un mensaje a nil generalmente devuelve nil a su vez, esto 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 y manejo de excepciones es una mejor manera de abordar este objective, pero no todos pueden estar de acuerdo con eso. (Por otro lado, por ejemplo, no me gusta el requisito de Java para que usted tenga que declarar qué excepciones puede arrojar un método, lo que a menudo lo obliga a propagar sintácticamente declaraciones de excepción en su código, pero eso es otra discusión).

He publicado una respuesta similar, pero más larga, a la pregunta relacionada “¿Afirma que cada creación de objeto tuvo éxito en el Objetivo C?” si quieres más detalles

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

Objective-C se basa en la representación de C de nada al agregar 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 objeta tienen para otros objetos comenzar como nulos, por lo que no es necesario, por ejemplo, establecer self. (Association) = nil en métodos init.

El comportamiento más notable de nada, sin embargo, es que puede tener mensajes enviados a él.

En otros lenguajes, como C ++ (o Java), esto bloquearía su progtwig, pero en Objective-C, invocar un método en nil devuelve un valor cero. Esto simplifica en gran medida las expresiones, ya que evita la necesidad de comprobar nada antes de hacer cualquier cosa:

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

Ser consciente de cómo nada funciona en Objective-C permite que esta conveniencia sea una característica, y no un error oculto en su aplicación. 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 (envío de mensajes a cero).