Objetivo-C: diferença entre id e nulo *

Respostas:

240

void * significa "uma referência a algum pedaço aleatório da memória com conteúdo não digitado / desconhecido"

id significa "uma referência a algum objeto Objective-C aleatório de classe desconhecida"

Existem diferenças semânticas adicionais:

  • Nos modos GC Only ou GC Supported, o compilador emitirá barreiras de gravação para referências do tipo id, mas não para o tipo void *. Ao declarar estruturas, isso pode ser uma diferença crítica. Declarar iVars como void *_superPrivateDoNotTouch;causará a colheita prematura de objetos, se _superPrivateDoNotTouchfor realmente um objeto. Não faça isso.

  • tentar invocar um método em uma referência do void *tipo exibirá um aviso do compilador.

  • tentar invocar um método em um idtipo só avisará se o método que está sendo chamado não tiver sido declarado em nenhuma das @interfacedeclarações vistas pelo compilador.

Portanto, nunca se deve se referir a um objeto como a void *. Da mesma forma, deve-se evitar o uso de uma idvariável digitada para se referir a um objeto. Use a referência digitada da classe mais específica possível. Even NSObject *é melhor do que idporque o compilador pode, pelo menos, fornecer uma melhor validação de invocações de método contra essa referência.

O uso comum e válido de void *é como uma referência de dados opaca que é passada por outra API.

Considere o sortedArrayUsingFunction: context:método de NSArray:

- (NSArray *)sortedArrayUsingFunction:(NSInteger (*)(id, id, void *))comparator context:(void *)context;

A função de classificação seria declarada como:

NSInteger mySortFunc(id left, id right, void *context) { ...; }

Nesse caso, o NSArray simplesmente passa o que você passa como contextargumento para o método e como contextargumento. É um pedaço opaco de dados do tamanho de ponteiros, no que diz respeito ao NSArray, e você é livre para usá-los para qualquer finalidade que desejar.

Sem um recurso de tipo de fechamento no idioma, essa é a única maneira de transportar um monte de dados com uma função. Exemplo; se você quiser que mySortFunc () classifique condicionalmente como distinção entre maiúsculas e minúsculas ou maiúsculas e minúsculas, embora ainda seja seguro para threads, você passaria o indicador de maiúsculas e minúsculas no contexto, provavelmente transmitindo a entrada e saída.

Frágil e propenso a erros, mas o único caminho.

Os blocos resolvem isso - os blocos são fechos para C. Eles estão disponíveis em Clang - http://llvm.org/ e são difundidos no Snow Leopard ( http://developer.apple.com/library/ios/documentation/Performance /Reference/GCD_libdispatch_Ref/GCD_libdispatch_Ref.pdf ).

bbum
fonte
Além disso, tenho certeza de que a idsuposta resposta é -retaine -release, enquanto a void*é completamente opaca ao chamado. Você não pode passar um ponteiro arbitrário para -performSelector:withObject:afterDelay:(ele retém o objeto) e não pode presumir que +[UIView beginAnimations:context:]reterá o contexto (o representante da animação deve manter a propriedade do contexto; o UIKit mantém o representante da animação).
tc.
3
Nenhuma suposição pode ser feita sobre o que a pessoa idresponde. Um idpode facilmente se referir a uma instância de uma classe que não é inerente a NSObject. Na prática, porém, sua afirmação combina melhor com o comportamento do mundo real; você não pode misturar <NSObject>classes não implementadas com a API Foundation e ir muito longe, isso é definitivamente certo!
bbum
2
Agora ide Classtipos estão a ser tratados como ponteiro objecto retainable sob ARC. Portanto, a suposição é verdadeira pelo menos no ARC.
eonil
O que é "colheita prematura"?
Brad Thomas
1
@BradThomas Quando um coletor de lixo coleta memória antes que o programa seja concluído.
bbum
21

id é um ponteiro para um objeto C objetivo, onde void * é um ponteiro para qualquer coisa.

id também desativa os avisos relacionados à chamada de métodos desconhecidos, por exemplo:

[(id)obj doSomethingWeirdYouveNeverHeardOf];

não emitirá o aviso usual sobre métodos desconhecidos. Obviamente, isso gerará uma exceção no tempo de execução, a menos que obj seja nulo ou realmente implemente esse método.

Freqüentemente, você deve usar NSObject*ou id<NSObject>preferir id, o que pelo menos confirma que o objeto retornado é um objeto Cocoa, para que você possa usar com segurança métodos como reter / liberar / liberar automaticamente.

Peter N Lewis
fonte
2
Para invocações de métodos, um destino do tipo (id) gerará um aviso se o método de destino não tiver sido declarado em nenhum lugar. Assim, no seu exemplo, doSomethingWeirdYouveNeverHeardOf teria que ter sido declarado em algum lugar para que não houvesse um aviso.
20/08/09
Você está certo, um exemplo melhor seria algo como storagePolicy.
Peter N Lewis
@ PeterNLewis Eu discordo em Often you should use NSObject*vez de id. Ao especificar, NSObject*você está realmente dizendo explicitamente que o objeto é um NSObject. Qualquer chamada de método para o objeto resultará em um aviso, mas nenhuma exceção de tempo de execução enquanto esse objeto realmente responder à chamada de método. O aviso é obviamente irritante, então idé melhor. De grosso modo, você pode ser mais específico, por exemplo id<MKAnnotation>, dizendo , que neste caso significa qualquer que seja o objeto, ele deve estar em conformidade com o protocolo MKAnnotation.
Pnizzle
1
Se você usar o ID <MKAnnotation>, poderá usar o NSObject <MKAnnotation> *. O que permite que você use qualquer um dos métodos no MKAnnotation e qualquer um dos métodos no NSObject (ou seja, todos os objetos na hierarquia normal da classe raiz do NSObject) e receba um aviso para qualquer outra coisa, o que é muito melhor do que nenhum aviso e uma falha de tempo de execução.
Pedro N Lewis
8

Se um método tiver um tipo de idretorno, você poderá retornar qualquer objeto Objective-C.

void significa que o método não retornará nada.

void *é apenas um ponteiro. Você não poderá editar o conteúdo no endereço apontado pelo ponteiro.

fphilipe
fonte
2
Como se aplica ao valor de retorno de um método, principalmente correto. Como se aplica à declaração de variáveis ​​ou argumentos, não é bem assim. E você sempre pode converter um (nulo *) em um tipo mais específico se quiser ler / escrever o conteúdo - não que seja uma boa idéia fazê-lo.
20/08/09
8

idé um ponteiro para um objeto Objective-C. void *é um ponteiro para qualquer coisa . Você pode usar em void *vez de id, mas não é recomendado, porque você nunca receberá avisos do compilador para nada.

Você pode querer ver stackoverflow.com/questions/466777/whats-the-difference-between-declaring-a-variable-id-and-nsobject e unixjunkie.blogspot.com/2008/03/id-vs-nsobject-vs -id.html .

Michael
fonte
1
Não é bem assim. As variáveis ​​digitadas (void *) não podem ser o alvo das invocações de métodos. Isso resulta em "aviso: tipo de receptor inválido 'void *'" do compilador.
20/08/09
@ album: void *variáveis ​​digitadas definitivamente podem ser o alvo de invocações de métodos - é um aviso, não um erro. Não só isso, você pode fazer isso: int i = (int)@"Hello, string!";e acompanhar com: printf("Sending to an int: '%s'\n", [i UTF8String]);. É um aviso, não um erro (e não é exatamente recomendado, nem portátil). Mas a razão pela qual você pode fazer essas coisas é tudo C. básica
Johne
1
Desculpe. Você está certo; é um aviso, não um erro. Eu apenas trato os avisos como erros sempre e em qualquer lugar.
bbum
4
/// Represents an instance of a class.
struct objc_object {
    Class isa  OBJC_ISA_AVAILABILITY;
};

/// A pointer to an instance of a class.
typedef struct objc_object *id;

O código acima é de objc.h, então parece que id é uma instância de objc_object struct e um ponteiro pode ser vinculado a qualquer objeto Objective C Class, enquanto void * é apenas um ponteiro sem tipo.

jack
fonte
2

Meu entendimento é que id representa um ponteiro para um objeto, enquanto void * pode apontar para algo realmente, desde que você o transmita para o tipo que deseja usá-lo como

hhafez
fonte
Se você estiver transmitindo de (void *) para algum tipo de objeto, incluindo id, provavelmente está fazendo algo errado. Existem razões para fazê-lo, mas são poucas, distantes entre si e quase sempre indicativas de uma falha de design.
20/08/09
1
citação "há razões para fazê-lo, mas são poucas, distantes entre" verdade isso. Depende da situação. No entanto, eu não faria uma declaração geral como "você provavelmente está fazendo errado" sem algum contexto.
hhafez
Eu faria uma declaração geral; teve que caçar e consertar muitos bugs malditos por causa da conversão para o tipo errado com o vazio * no meio. A única exceção são as APIs baseadas em retorno de chamada que recebem um argumento de contexto nulo *, cujo contrato afirma que o contexto permanecerá intocado entre a configuração do retorno de chamada e o recebimento da chamada de retorno.
20/08/09
0

Além do que já foi dito, há uma diferença entre objetos e ponteiros relacionados a coleções. Por exemplo, se você quiser inserir algo no NSArray, precisará de um objeto (do tipo "id") e não poderá usar um ponteiro de dados brutos (do tipo "void *"). Você pode usar [NSValue valueWithPointer:rawData]para converter void *rawDdatapara o tipo "id" para usá-lo dentro de uma coleção. Em geral, "id" é mais flexível e tem mais semântica relacionada a objetos anexados a ele. Há mais exemplos explicando o tipo de identificação do objetivo C aqui .

battlmonstr
fonte