Swift y Objective-C

Objective-C nil frente a Java null: ¿por qué se comportan de forma diferente?

Hay algo que desconcierta especialmente a quienes empiezan con iOS.

4 min de lectura
Imagen de portada de Objective-C nil frente a Java null: ¿por qué se comportan de forma diferente?

Hay algo que desconcierta especialmente a quienes empiezan con iOS.

En Java o C++, intentar hacer algo con un objeto nil (null) provoca que la app se cierre de inmediato. La famosa NullPointerException.

Pero en Objective-C, enviar un mensaje a nil es silencioso. No hay cierre inesperado ni error.

Al principio puedes pensar: «¿Esto no es un bug?». En realidad, el lenguaje está diseñado así deliberadamente.

En resumen, enviar un mensaje a nil en Objective-C no hace nada y simplemente devuelve 0 (o nil).

No se trata de ignorar los errores, sino de un mecanismo de seguridad diseñado intencionadamente a nivel del lenguaje.

Hoy veremos por qué es posible este comportamiento y cómo conviene entenderlo.


¿Por qué enviar un mensaje a nil no provoca un cierre inesperado?

La clave está en la forma en que Objective-C envía mensajes.

Cuando escribimos código como [object doSomething], el compilador lo convierte en una llamada a la función objc_msgSend(object, @selector(doSomething)).

No se llama directamente al método; se le pide al runtime: «Entrega este mensaje a este objeto».

Sin embargo, la función objc_msgSend comprueba primero si el objeto receptor es nil.

El punto en el que objc_msgSend absorbe nil silenciosamente
El punto en el que objc_msgSend absorbe nil silenciosamente

¿Qué ocurre si el objeto receptor es nil?

No busca el método: devuelve 0 inmediatamente y termina en silencio.

En otras palabras, nil es algo que «se traga los mensajes». Como el mensaje no llega a ninguna parte, tampoco puede producirse un cierre.

// receiversi es nilno busca el método y devuelve 0 de inmediato
NSString *name = nil;
NSUInteger len = [name length];  // sin cierre inesperado len == 0

¿En qué se diferencia de NullPointerException?

Es un punto que suele generar confusión, así que lo he resumido en una tabla.

Categoría Objective-C (nil) Java (null)
Llamar a un método sobre null Ignorar y devolver 0/nil Se produce NullPointerException
Comportamiento de la app Continúa sin cerrarse Se cierra por una excepción
Responsable del tratamiento Runtime (objc_msgSend) Gestión de excepciones de la JVM

En cuanto Java accede a una referencia null, lanza una excepción como diciendo: «Ese objeto no existe».

En cambio, el runtime de Objective-C trata nil como un valor normal.

Por eso, cuando los desarrolladores de Java pasan a Objective-C, suelen sentirse incómodos durante un tiempo debido a esta diferencia.

El valor devuelto también varía ligeramente según el tipo. Un tipo de objeto devuelve nil, un tipo numérico devuelve 0 y una estructura devuelve un valor rellenado con ceros.


¿Que no se cierre siempre es algo bueno?

Sinceramente, es un arma de doble filo.

La ventaja es evidente: no hace falta llenar el código de comprobaciones de nil.

Por ejemplo, aunque un array vacío devuelva nil, pedirle count simplemente produce 0, así que el flujo no se interrumpe.

Pero precisamente ese aspecto también puede convertirse en una trampa.

Los bugs no aparecen como cierres inesperados, sino que quedan ocultos silenciosamente.

Cuando los datos no aparecen en pantalla y pasas mucho tiempo investigando, a menudo se debe a que algún objeto se convirtió en nil y todos sus mensajes fueron ignorados.

Si se produjera un cierre, lo encontrarías enseguida; al pasar desapercibido, rastrear la causa lleva más tiempo.

Una ejecución rápida permite comprobar si realmente no se cierra
Una ejecución rápida permite comprobar si realmente no se cierra

Por eso es importante adquirir el hábito de distinguir entre los lugares donde nil puede fluir y aquellos donde debe existir un valor.


¿Cómo conviene gestionarlo en la práctica?

Estas son algunas técnicas que utilizo.

  1. Comprobar nil explícitamente en las bifurcaciones importantes (if (object == nil))
  2. Indicar mediante comentarios o nombres qué métodos pueden devolver nil
  3. Si sospechas de un nil inesperado, detectarlo durante el desarrollo con NSAssert

Al pasar a Swift, la historia vuelve a ser diferente.

Swift utiliza el concepto de Optional para dejar fijada la posibilidad de nil en el sistema de tipos.

Los valores que pueden ser nil deben marcarse con un signo de interrogación y también deben gestionarse de forma segura al desenvolverlos.

En cierto modo, Swift convierte el «pasar silenciosamente» de Objective-C en «hacerlo visible de antemano».

Al compararlo lado a lado con Swift, se aprecia la diferencia de filosofía de diseño
Al compararlo lado a lado con Swift, se aprecia la diferencia de filosofía de diseño

Que Objective-C no se cierre al enviar mensajes a nil no es un bug, sino una filosofía.

La comodidad también puede ocultar bugs silenciosos; entender «¿por qué no se cierra?» te permite escribir código más sólido.

Si ahora te encuentras con una pantalla en blanco sin causa aparente en tu código iOS, comprueba si nil está pasando silenciosamente por algún punto intermedio. Seguro que te ayuda.

Seguir leyendo