Por que e não ?

Em Objective-C, por que [object doSomething] ? Não seria [*object doSomething] já que você está chamando um método no object ?, o que significa que você deve desreferenciar o ponteiro?

A resposta remonta às raízes C do Objective-C. Objetivo-C foi originalmente escrito como um pré-processador de compilador para C. Ou seja, Objective-C não foi compilado tanto quanto foi transformado em C direto e então compilado.

Comece com a definição do tipo id . É declarado como:

 typedef struct objc_object { Class isa; } *id; 

Ou seja, um id é um ponteiro para uma estrutura cujo primeiro campo é do tipo Class (que, ele próprio, é um ponteiro para uma estrutura que define uma class). Agora, considere o NSObject :

 @interface NSObject  { Class isa; } 

Observe que o layout do NSObject e o layout do tipo apontado por id são idênticos. Isso porque, na realidade, uma instância de um object Objective-C é realmente apenas um ponteiro para uma estrutura cujo primeiro campo – sempre um ponteiro – aponta para a class que contém os methods para essa instância (juntamente com alguns outros metadados ).

Quando você subclass NSObject e adiciona algumas variables ​​de instância, você está, para todos os efeitos, simplesmente criando uma nova estrutura C que contenha suas variables ​​de instância como slots naquela estrutura concatenada nos slots para as variables ​​de instância para todas as superclasss. (O tempo de execução moderno funciona de forma ligeiramente diferente, de modo que uma superclass pode ter ivars anexados sem exigir que todas as subclasss sejam recompiladas).

Agora, considere a diferença entre essas duas variables:

 NSRect foo; NSRect *bar; 

(NSRect sendo uma estrutura C simples – não há ObjC envolvido). foo é criado com o armazenamento na pilha. Ele não sobreviverá quando o quadro de pilha estiver fechado, mas você também não precisará liberar nenhuma memory. bar é uma referência a uma estrutura NSRect que foi, provavelmente, criada no heap usando malloc() .

Se você tentar dizer:

 NSArray foo; NSArray *bar; 

O compilador irá reclamar sobre o primeiro, dizendo que algo ao longo das linhas de objects baseados em pilha não é permitido em Objective-C . Em outras palavras, todos os objects Objective-C devem ser alocados do heap (mais ou menos – há uma ou duas exceções, mas eles são comparativamente esotéricos para essa discussão) e, como resultado, você sempre se refere a um object através de o endereço do dito object no heap; você está sempre trabalhando com pointers para objects (e o tipo de id realmente é apenas um ponteiro para qualquer object antigo).

Voltando às raízes do pré-processador C da linguagem, você pode traduzir cada chamada de método para uma linha equivalente de C. Por exemplo, as duas linhas de código a seguir são idênticas:

 [myArray objectAtIndex: 42]; objc_msgSend(myArray, @selector(objectAtIndex:), 42); 

Da mesma forma, um método declarado assim:

 - (id) objectAtIndex: (NSUInteger) a; 

É equivalente à function C declarada assim:

 id object_at_index(id self, SEL _cmd, NSUInteger a); 

E, olhando para objc_msgSend() , o primeiro argumento é declarado como sendo do tipo id :

 OBJC_EXPORT id objc_msgSend(id self, SEL op, ...); 

E é exatamente por isso que você não usa *foo como alvo de uma chamada de método. Faça a tradução através dos formulários acima – a chamada para [myArray objectAtIndex: 42] é traduzida para a chamada de function C acima, que deve então chamar algo com a declaração de chamada de function C equivalente (todos vestidos na syntax do método).

A referência do object é realizada porque dá ao messenger – objc_msgSend () access à class para então encontrar a implementação do método – assim como aquela referência se tornando o primeiro parâmetro – o self – do método que é eventualmente executado.

Se você realmente quer ir fundo, comece por aqui . Mas não se incomode até que você tenha totalmente grokked isso .

Você não deveria pensar neles como pointers para objects. É uma espécie de detalhe de implementação histórica que eles são pointers, e que você os usa assim na syntax de envio de mensagens (veja a resposta do @bbum). Na verdade, eles são apenas “identificadores de objects” (ou referências). Vamos retroceder um pouco para ver a lógica conceitual.

Objetivo-C foi proposto pela primeira vez e discutido neste livro: Programação Orientada a Objetos: Uma Abordagem Evolutiva . Não é imensamente prático para programadores modernos de cacau, mas as motivações para o idioma estão lá.

Note que no livro todos os objects recebem o id tipo. Você não vê os Object * mais específicos no livro; esses são apenas um vazamento na abstração quando estamos falando sobre o “porquê”. Veja o que o livro diz:

Os identificadores de objects devem identificar de forma exclusiva quantos objects puderem coexistir no sistema a qualquer momento. Eles são armazenados em variables ​​locais, passados ​​como argumentos em expressões de mensagem e em chamadas de function, mantidos em variables ​​de instância (campos dentro de objects) e em outros tipos de estruturas de memory. Em outras palavras, elas podem ser usadas de forma tão fluida quanto os tipos internos da linguagem base.

Como um identificador de object realmente identifica o object é um detalhe de implementação para o qual muitas opções são plausíveis. Uma escolha razoável, certamente uma das mais simples, e a que é usada em Objective-C, é usar o endereço físico do object na memory como seu identificador. O Objective-C torna essa decisão conhecida por C, gerando uma instrução typedef em cada arquivo. Isso define um novo tipo, id, em termos de outro tipo que C já entende, ou seja, pointers para estruturas. […]

Um id consome uma quantidade fixa de espaço. […] Este espaço não é o mesmo que o espaço ocupado pelos dados privados no próprio object.

(pp58-59, 2a ed.)

Então a resposta para sua pergunta é dupla:

  1. O design da linguagem especifica que o identificador de um object não é o mesmo que um object em si, e o identificador é a coisa para a qual você envia mensagens, não o próprio object.
  2. O design não dita, mas sugere, a implementação que temos agora, onde os pointers para objects são usados ​​como identificadores.

A syntax estritamente tipada onde você diz “um object especificamente do tipo NSString” e, portanto, usa NSString * é uma mudança mais moderna, e é basicamente uma opção de implementação, equivalente a id .

Se isso parecer uma resposta de alto nível a uma pergunta sobre desreferenciamento de ponteiro, é importante ter em mente que os objects em Objective-C são “especiais” de acordo com a definição da linguagem. Eles são implementados como estruturas e passados ​​como pointers para estruturas, mas são conceitualmente diferentes.

Porque objc_msgSend () é declarado assim:

 id objc_msgSend(id theReceiver, SEL theSelector, ...) 
  1. Não é um ponteiro, é uma referência a um object.
  2. Não é um método, é uma mensagem.

Você nunca desrefere pointers de object, ponto final. O fato de serem typescripts como pointers em vez de apenas “tipos de object” é um artefato da inheritance C da linguagem. É exatamente equivalente ao sistema de tipos do Java, onde os objects são sempre acessados ​​por meio de referências. Você nunca desreferencia um object em Java – na verdade, você não pode. Você não deve pensar neles como pointers, porque semanticamente eles não são. Eles são apenas referências de objects.

Parte do motivo é que você obteria exceções de ponteiro nulo para a esquerda e para a direita. Enviar uma mensagem para nil é permitido e muitas vezes perfeitamente legítimo (não faz nada e não gera um erro).

Mas você pode pensar nisso como análogo à notação de C ++ -> : Executa o método e desreferencia o ponteiro em um pedaço de açúcar sintático.

Eu diria assim: o que uma linguagem associa a uma série de alfabetos é apenas uma convenção. As pessoas que projetaram o Objective-C decidiram que

 [x doSomething]; 

significa “enviar a mensagem doSomething para o object apontado por x”. Eles definiram dessa forma, você segue a regra 🙂 Uma peculiaridade do Objective-C, comparado a, por exemplo, C ++, é que ele não tem uma syntax para manter um object em si, não um ponteiro para o object. Assim,

 NSString* string; 

está bem, mas

 NSString string; 

é ilegal. Se o último fosse possível, teria de haver uma maneira de “enviar a mensagem capitalizedString para uma string de string “, e não “enviar a mensagem capitalizedString para uma string apontada por string “. Mas, na realidade, você sempre envia uma mensagem para um object apontado por uma variável em seu código-fonte.

Então, se os projetistas do Objective-C tivessem seguido sua lógica, você teria que escrever

 [*x doSomething]; 

toda vez que você envia uma mensagem … Você vê, * precisa aparecer sempre após o colchete de comando [ , formando a combinação [* . Nesse estágio, acredito que você concorda que é melhor redesenhar a linguagem para que você só tenha que escrever [ vez de [* , alterando o significado da sequência de letras [x doSomething] .

Um object no Objective-C é essencialmente uma struct . O primeiro membro da estrutura é a Class isa (o tamanho total da struct pode ser determinado usando isa ). Membros subseqüentes da struct podem include variables ​​de instância.

Quando você declara um object Objective-C, você sempre o declara como um tipo de ponteiro porque o tempo de execução fornece seu object para outros methods e funções; se estes alterarem quaisquer membros da struct (modificando variables ​​de instância, etc.), eles serão “aplicados” a todas as referências ao seu object, e não apenas localmente dentro do método ou function.

O tempo de execução do Objective-C pode precisar devolver o object a algumas funções diferentes, de modo que ele deseje a referência do object, e não o próprio object.