Enviando uma mensagem para nil em Objective-C

Como um desenvolvedor Java que está lendo a documentação do Objective-C 2.0 da Apple: Eu me pergunto o que significa ” enviar uma mensagem para zero ” – e muito menos como é realmente útil. Tomando um trecho da documentação:

Existem vários padrões em Cocoa que aproveitam esse fato. O valor retornado de uma mensagem para zero também pode ser válido:

  • Se o método retornar um object, qualquer tipo de ponteiro, qualquer inteiro escalar de tamanho menor ou igual a sizeof (void *), um float, um double, um long double ou um long long, uma mensagem enviada para nil retornará 0 .
  • Se o método retornar uma struct, conforme definido pelo Guia de Chamadas de Função ABI do Mac OS X a ser retornado nos registradores, uma mensagem enviada para nil retornará 0.0 para cada campo na estrutura de dados. Outros tipos de dados de estruturas não serão preenchidos com zeros.
  • Se o método retornar algo diferente dos tipos de valores mencionados acima, o valor de retorno de uma mensagem enviada para nil será indefinido.

O Java tornou meu cérebro incapaz de obter a explicação acima? Ou há algo que estou perdendo que tornaria isso tão claro quanto o vidro?

Eu tenho a idéia de mensagens / receptores em Objective-C, estou simplesmente confuso sobre um receptor que é nil .

Bem, acho que isso pode ser descrito usando um exemplo muito artificial. Digamos que você tenha um método em Java que imprima todos os elementos em uma ArrayList:

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

Agora, se você chamar esse método da seguinte forma: someObject.foo (NULL); você provavelmente obterá uma NullPointerException quando tentar acessar a lista, nesse caso na chamada para list.size (); Agora, você provavelmente nunca chamaria someObject.foo (NULL) com o valor NULL assim. No entanto, você pode ter obtido seu ArrayList de um método que retorna NULL se ele for executado em algum erro gerando o ArrayList como someObject.foo (otherObject.getArrayList ());

Claro, você também terá problemas se fizer algo assim:

 ArrayList list = NULL; list.size(); 

Agora, em Objective-C, temos o método equivalente:

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

Agora, se tivermos o seguinte código:

 [someObject foo:nil]; 

temos a mesma situação em que o Java produzirá um NullPointerException. O object nulo será acessado primeiro em [anArray count] No entanto, em vez de lançar um NullPointerException, o Objective-C retornará 0 de acordo com as regras acima, portanto, o loop não será executado. No entanto, se definirmos o loop para executar um número definido de vezes, enviaremos uma mensagem para anArray em [anArray objectAtIndex: i]; Isso também retornará 0, mas como objectAtIndex: retorna um ponteiro, e um ponteiro para 0 é nil / NULL, NSLog será passado nil a cada vez através do loop. (Embora NSLog seja uma function e não um método, ela é impressa (nula) se passar por uma NSString nula.

Em alguns casos, é melhor ter um NullPointerException, já que você pode dizer imediatamente que algo está errado com o programa, mas a menos que você pegue a exceção, o programa irá travar. (Em C, tentar desreferenciar NULL dessa maneira faz com que o programa trave.) Em Objective-C, ele apenas causa um comportamento de tempo de execução possivelmente incorreto. No entanto, se você tiver um método que não quebra se ele retorna 0 / nil / NULL / a zeroed struct, isso evita que você tenha que verificar se o object ou parâmetros são nulos.

Uma mensagem para nil não faz nada e retorna nil , Nil , NULL , 0 ou 0.0 .

Todos os outros posts estão corretos, mas talvez seja o conceito que é importante aqui.

Nas chamadas de método Objective-C, qualquer referência de object que possa aceitar um seletor é um destino válido para esse seletor.

Isso economiza muito “é o object alvo do tipo X?” código – desde que o object receptor implemente o seletor, não faz diferença alguma que class é! nil é um NSObject que aceita qualquer seletor – ele simplesmente não faz nada. Isso elimina um monte de “cheque para nulo, não envie a mensagem se verdadeiro” código também. (O conceito “se aceitar, ele implementa” também é o que permite criar protocolos , que são meio parecidos com interfaces Java: uma declaração de que, se uma class implementa os methods declarados, ela está em conformidade com o protocolo.)

A razão para isso é eliminar o código do macaco que não faz nada exceto manter o compilador feliz. Sim, você obtém a sobrecarga de mais uma chamada de método, mas economiza tempo do programador , que é um recurso muito mais caro que o tempo de CPU. Além disso, você está eliminando mais códigos e mais complexidade condicional do seu aplicativo.

Esclarecendo para downvoters: você pode achar que este não é um bom caminho a percorrer, mas é como a linguagem é implementada, e é o idioma de programação recomendado em Objective-C (veja as palestras de programação do iPhone de Stanford).

O que isso significa é que o tempo de execução não produz um erro quando objc_msgSend é chamado no ponteiro nulo; em vez disso, retorna algum valor (frequentemente útil). Mensagens que podem ter um efeito colateral não fazem nada.

É útil porque a maioria dos valores padrão é mais apropriada que um erro. Por exemplo:

 [someNullNSArrayReference count] => 0 

Ou seja, nil parece ser o array vazio. Ocultar uma referência NSView nula não faz nada. Handy, hein?

Na cotação da documentação, há dois conceitos separados – talvez seja melhor se a documentação deixar isso mais claro:

Existem vários padrões em Cocoa que aproveitam esse fato.

O valor retornado de uma mensagem para zero também pode ser válido:

O primeiro é provavelmente mais relevante aqui: normalmente, ser capaz de enviar mensagens para nil torna o código mais direto – você não precisa verificar valores nulos em todos os lugares. O exemplo canônico é provavelmente o método do acessador:

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

Se o envio de mensagens para nil não fosse válido, esse método seria mais complexo. Você precisaria ter duas verificações adicionais para garantir que value e newValue não sejam nil antes de enviar mensagens.

O último ponto (que os valores retornados de uma mensagem para nil também são tipicamente válidos), no entanto, adiciona um efeito multiplicador ao primeiro. Por exemplo:

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

Esse código novamente não requer uma verificação de valores nil e flui naturalmente …

Tudo isso dito, a flexibilidade adicional que ser capaz de enviar mensagens a nil vem a algum custo. Existe a possibilidade de que você, em algum momento, escreva um código que falha de uma forma peculiar, porque você não levou em conta a possibilidade de que um valor possa ser nil .

Do site de Greg Parker :

Se estiver executando o LLVM Compiler 3.0 (Xcode 4.2) ou posterior

 Mensagens para zero com tipo de retorno |  Retorna
 Inteiros até 64 bits |  0
 Ponto flutuante até ao dobro longo |  0,0
 Ponteiros |  nada
 Estruturas |  {0}
 Qualquer tipo _Complex |  {0, 0}

Isso significa muitas vezes não ter que verificar se há objects nulos em todos os lugares por segurança – particularmente:

 [someVariable release]; 

ou, como observado, vários methods de contagem e comprimento retornam 0 quando você tem um valor nulo, portanto você não precisa adicionar cheques extras para todos os valores:

 if ( [myString length] > 0 ) 

ou isto:

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

Não pense em “o receptor ser nulo”; Eu concordo, isso é bem estranho. Se você está enviando uma mensagem para zero, não há receptor. Você está apenas enviando uma mensagem para nada.

Como lidar com isso é uma diferença filosófica entre Java e Objective-C: em Java, isso é um erro; em Objective-C, é um não-op.

Mensagens ObjC que são enviadas para nil e cujos valores de retorno possuem tamanho maior que sizeof (void *) produzem valores indefinidos em processadores PowerPC. Além disso, essas mensagens fazem com que valores indefinidos sejam retornados em campos de estruturas cujo tamanho é maior que 8 bytes em processadores Intel também. Vincent Gable descreveu isso muito bem em seu post no blog

Eu não acho que nenhuma das outras respostas tenha mencionado isso claramente: se você está acostumado com Java, você deve ter em mente que enquanto o Objective-C no Mac OS X possui suporte a exception handling, é um recurso de linguagem opcional que pode ser ativado / desativado com um sinalizador de compilador. Meu palpite é que esse design de “enviar mensagens para nil é seguro” é anterior à inclusão do suporte a exception handling no idioma e foi feito com um objective semelhante em mente: methods podem retornar nil para indicar erros e enviar uma mensagem para nil geralmente retorna nil por sua vez, isso permite que a indicação de erro se propague através de seu código, para que você não precise checá-lo em cada mensagem. Você só precisa verificar isso nos pontos em que isso é importante. Pessoalmente, acho que a propagação e o tratamento de exceções são a melhor maneira de abordar esse objective, mas nem todos concordam com isso. (Por outro lado, eu, por exemplo, não gosto do requisito de Java para declarar quais exceções um método pode lançar, o que geralmente força você a propagar sintaticamente declarações de exceção em todo o seu código; mas isso é outra discussão.)

Eu publiquei uma resposta semelhante, mas mais longa, à questão relacionada “Está afirmando que toda a criação de objects foi necessária no Objetivo C?” se você quiser mais detalhes.

C representa nada como 0 para valores primitivos e NULL para pointers (que é equivalente a 0 em um contexto de ponteiro).

Objective-C baseia-se na representação de C de nada, adicionando nil. nil é um ponteiro de object para nada. Embora semanticamente distintos de NULL, eles são tecnicamente equivalentes entre si.

NSObjects recém-alocados iniciam a vida com seu conteúdo definido como 0. Isso significa que todos os pointers que o object tem para outros objects começam como nulo, portanto é desnecessário, por exemplo, definir self. (Association) = nil nos methods init.

O comportamento mais notável de nil, porém, é que ele pode ter mensagens enviadas para ele.

Em outras linguagens, como C ++ (ou Java), isso iria travar seu programa, mas em Objective-C, invocar um método em nil retorna um valor zero. Isso simplifica muito as expressões, pois elimina a necessidade de verificar nil antes de fazer qualquer coisa:

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

Estar ciente de como nil funciona em Objective-C permite que essa conveniência seja um recurso, e não um bug à espreita em seu aplicativo. Certifique-se de proteger contra casos em que valores nulos não são desejados, seja marcando e retornando cedo para falhar silenciosamente ou incluindo um NSParameterAssert para lançar uma exceção.

Fonte: http://nshipster.com/nil/ https://developer.apple.com/library/ios/#documentation/cocoa/conceptual/objectivec/Chapters/ocObjectsClasses.html (Envio de mensagem para zero).