Swift e Objective-C

nil do Objective-C vs. null do Java: por que eles se comportam de forma diferente?

Há algo que deixa quem está começando com iOS especialmente confuso.

4 min de leitura
Imagem de capa de nil do Objective-C vs. null do Java: por que eles se comportam de forma diferente?

Há algo que deixa quem está começando com iOS especialmente confuso.

Em Java ou C++, tentar fazer algo com um objeto nil (null) faz o app travar imediatamente. A famosa NullPointerException.

Mas, no Objective-C, enviar uma mensagem para nil é silencioso. Não há crash nem erro.

No começo, você pode pensar: “Isso não é um bug?”. Na verdade, a linguagem foi projetada deliberadamente dessa forma.

Em resumo, enviar uma mensagem para nil no Objective-C não faz nada e simplesmente retorna 0 (ou nil).

Isso não significa deixar erros de lado, mas usar um mecanismo de segurança criado intencionalmente no nível da linguagem.

Hoje vamos entender por que esse comportamento é possível e como enxergá-lo da melhor forma.


Por que enviar uma mensagem para nil não causa crash?

O ponto central está na forma como o Objective-C envia mensagens.

Quando escrevemos um código como [object doSomething], o compilador o transforma em uma chamada de função chamada objc_msgSend(object, @selector(doSomething)).

Em vez de chamar o método diretamente, o código pede ao runtime: “Entregue esta mensagem a este objeto”.

Mas a função objc_msgSend verifica primeiro se o objeto receptor é nil.

O ponto em que objc_msgSend engole nil silenciosamente
O ponto em que objc_msgSend engole nil silenciosamente

E se o objeto receptor for nil?

Ele não procura o método: retorna 0 imediatamente e termina em silêncio.

Em outras palavras, nil é algo que “engole mensagens”. Como a mensagem não chega a lugar algum, nada trava.

// receiverse for nilnão procura o método e retorna 0 imediatamente
NSString *name = nil;
NSUInteger len = [name length];  // sem crash len == 0

Qual é a diferença em relação à NullPointerException?

Esse é um ponto que costuma gerar confusão, então organizei tudo em uma tabela.

Categoria Objective-C (nil) Java (null)
Chamar um método em null Ignorar e retornar 0/nil Lançar NullPointerException
Comportamento do app Continua sem travar Trava por causa de uma exceção
Responsável pelo tratamento Runtime (objc_msgSend) Tratamento de exceções da JVM

No momento em que o Java acessa uma referência null, ele lança uma exceção, como se dissesse: “Esse objeto não existe”.

Já o runtime do Objective-C trata nil como um valor normal.

Por isso, desenvolvedores Java que migram para Objective-C costumam estranhar essa diferença por algum tempo.

O valor de retorno também varia um pouco conforme o tipo. Um tipo de objeto retorna nil, um tipo numérico retorna 0 e uma struct retorna um valor preenchido com zeros.


Não travar é sempre uma coisa boa?

Sinceramente, é uma faca de dois gumes.

A vantagem é clara: você não precisa encher o código de verificações de nil.

Por exemplo, mesmo que um array vazio retorne nil, perguntar por count simplesmente produz 0, então o fluxo não é interrompido.

Mas esse mesmo ponto também pode virar uma armadilha.

Os bugs não aparecem como crashes; eles ficam ocultos silenciosamente.

Quando os dados não aparecem na tela e você passa muito tempo investigando, muitas vezes é porque algum objeto virou nil no caminho e todas as mensagens foram ignoradas.

Se houvesse um crash, seria fácil encontrar; como tudo segue silenciosamente, rastrear a causa demora mais.

Executar diretamente confirma rapidamente se ele realmente não trava
Executar diretamente confirma rapidamente se ele realmente não trava

Por isso, é importante criar o hábito de distinguir os lugares em que nil pode passar com segurança daqueles em que um valor é obrigatório.


Como lidar com isso no dia a dia?

Aqui estão algumas abordagens que uso.

  1. Verifique nil explicitamente em ramificações importantes (if (object == nil))
  2. Indique em comentários ou nomes os métodos que podem retornar nil
  3. Se houver suspeita de um nil inesperado, detecte-o na fase de desenvolvimento com NSAssert

Ao migrar para Swift, a história muda novamente.

Swift usa o conceito de Optional para deixar a possibilidade de nil explícita no sistema de tipos.

Valores que podem ser nil precisam ser marcados com um ponto de interrogação e também devem ser tratados com segurança ao serem desembrulhados.

Em outras palavras, Swift transforma o “seguir silenciosamente” do Objective-C em “deixar explícito de antemão”.

Ao comparar lado a lado com Swift, fica clara a diferença na filosofia de design
Ao comparar lado a lado com Swift, fica clara a diferença na filosofia de design

O fato de o Objective-C não travar ao receber mensagens em nil não é um bug, mas uma filosofia.

A conveniência também pode esconder bugs silenciosos; depois de entender “por que não trava?”, você consegue escrever um código mais robusto.

Se você está diante de uma tela em branco sem causa aparente no código iOS, verifique se nil não está passando silenciosamente por algum ponto intermediário. Isso certamente ajudará.

Continue lendo