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 ).


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.


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.


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 .


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.


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


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 .

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.