Obiettivo-C: differenza tra id e vuoto *


Risposte:


240

void * significa "un riferimento ad una parte casuale della memoria con contenuti non tipizzati / sconosciuti"

id significa "un riferimento ad un oggetto casuale Objective-C di classe sconosciuta"

Ci sono ulteriori differenze semantiche:

  • In modalità Solo GC o GC supportate, il compilatore emetterà barriere di scrittura per riferimenti di tipo id, ma non per tipo void *. Nel dichiarare le strutture, questa può essere una differenza critica. Dichiarare iVars come questo void *_superPrivateDoNotTouch;causerà la raccolta prematura di oggetti se in _superPrivateDoNotTouchrealtà è un oggetto. Non farlo.

  • il tentativo di invocare un metodo su un riferimento di void *tipo genererà un avviso del compilatore.

  • il tentativo di invocare un metodo su un idtipo avviserà solo se il metodo chiamato non è stato dichiarato in nessuna delle @interfacedichiarazioni viste dal compilatore.

Pertanto, non si dovrebbe mai riferirsi a un oggetto come a void *. Allo stesso modo, si dovrebbe evitare di usare una idvariabile tipizzata per fare riferimento a un oggetto. Usa il riferimento tipizzato di classe più specifico che puoi. Anche NSObject *è meglio che idperché il compilatore può, almeno, fornire una migliore convalida delle invocazioni del metodo rispetto a quel riferimento.

L'utilizzo comune e valido di void *è come riferimento di dati opachi che viene passato attraverso qualche altra API.

Considera il sortedArrayUsingFunction: context:metodo di NSArray:

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

La funzione di ordinamento sarebbe dichiarata come:

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

In questo caso, NSArray passa semplicemente qualsiasi cosa passi come contextargomento al metodo come contextargomento. È un blocco opaco di dati di dimensioni puntatore, per quanto riguarda NSArray, e sei libero di usarlo per qualsiasi scopo tu voglia.

Senza una funzionalità di tipo di chiusura nella lingua, questo è l'unico modo per trasportare un pezzo di dati con una funzione. Esempio; se si desidera che mySortFunc () venga ordinato in modo condizionale come case sensitive o case sensitive, pur essendo comunque thread-safe, si passerà l'indicatore sensibile al maiuscolo / minuscolo nel contesto, probabilmente lanciandosi lungo la strada per entrare e uscire.

Fragile e soggetto a errori, ma l'unico modo.

I blocchi risolvono questo problema - I blocchi sono chiusure per C. Sono disponibili in Clang - http://llvm.org/ e sono diffusi in Snow Leopard ( http://developer.apple.com/library/ios/documentation/Performance /Reference/GCD_libdispatch_Ref/GCD_libdispatch_Ref.pdf ).


Inoltre, sono abbastanza sicuro che idsi supponga che an risponda -retaine -release, mentre a void*è completamente opaco per la chiamata. Non è possibile passare un puntatore arbitrario a -performSelector:withObject:afterDelay:(mantiene l'oggetto) e non si può presumere che +[UIView beginAnimations:context:]manterrà il contesto (il delegato all'animazione dovrebbe mantenere la proprietà del contesto; UIKit mantiene il delegato all'animazione).
tc.

3
Non si possono fare ipotesi su ciò a cui si idrisponde. Un idpuò facilmente fare riferimento a un'istanza di una classe che non è inerente NSObject. In pratica, tuttavia, la tua affermazione si adatta meglio al comportamento del mondo reale; non puoi mescolare <NSObject>classi non implementabili con l'API Foundation e andare molto lontano, questo è sicuramente certo!
bbum,

2
Ora ide Classtipi vengono trattati come puntatore a oggetto conservabile in ARC. Quindi il presupposto è vero almeno sotto ARC.
eonil,

Che cos'è il "raccolto precoce"?
Brad Thomas,

1
@BradThomas Quando un Garbage Collector raccoglie la memoria prima che il programma venga terminato.
bbum

21

id è un puntatore a un oggetto C obiettivo, dove void * è un puntatore a qualsiasi cosa.

id disattiva anche gli avvisi relativi alla chiamata di mthod sconosciuti, quindi ad esempio:

[(id)obj doSomethingWeirdYouveNeverHeardOf];

non darà il solito avvertimento su metodi sconosciuti. Ovviamente genererà un'eccezione in fase di esecuzione a meno che obj non sia nullo o non applichi davvero quel metodo.

Spesso dovresti usare NSObject*o id<NSObject>preferire id, il che conferma almeno che l'oggetto restituito è un oggetto Cocoa, quindi puoi tranquillamente usare metodi come fidelizzare / rilasciare / rilasciare automaticamente su di esso.


2
Per le invocazioni di metodo, una destinazione di tipo (id) genererà un avviso se il metodo di destinazione non è stato dichiarato da nessuna parte. Quindi, nel tuo esempio, doSomethingWeirdYouveNeverHeardOf avrebbe dovuto essere dichiarato da qualche parte per non esserci un avvertimento.
bbum

Hai ragione, un esempio migliore sarebbe qualcosa come storagePolicy.
Peter N Lewis,

@PeterNLewis Non sono d'accordo Often you should use NSObject*invece di id. Specificando, NSObject*stai effettivamente dicendo esplicitamente che l'oggetto è un oggetto NSO. Qualsiasi chiamata di metodo all'oggetto genererà un avviso, ma nessuna eccezione di runtime fintanto che l'oggetto risponde effettivamente alla chiamata di metodo. L'avvertimento è ovviamente fastidioso, quindi idè meglio. Di massima puoi quindi essere più specifico dicendo ad esempio che id<MKAnnotation>, in questo caso, qualunque sia l'oggetto, deve essere conforme al protocollo MKAnnotation.
pnizzle

1
Se stai per usare id <MKAnnotation>, puoi anche usare NSObject <MKAnnotation> *. Ciò consente quindi di utilizzare uno qualsiasi dei metodi nella MKAnnotation e uno dei metodi in NSObject (ovvero tutti gli oggetti nella normale gerarchia della classe radice NSObject) e di ottenere un avviso per qualsiasi altra cosa, che è molto meglio di nessun avviso e un arresto anomalo del runtime.
Peter N Lewis,

8

Se un metodo ha un tipo di idritorno, è possibile restituire qualsiasi oggetto Objective-C.

void significa che il metodo non restituirà nulla.

void *è solo un puntatore. Non sarai in grado di modificare il contenuto sull'indirizzo a cui punta il puntatore.


2
Come si applica al valore di ritorno di un metodo, per lo più giusto. Come si applica alla dichiarazione di variabili o argomenti, non del tutto. E puoi sempre trasmettere un (void *) a un tipo più specifico se vuoi leggere / scrivere il contenuto - non che sia una buona idea farlo.
bbum

8

idè un puntatore a un oggetto Objective-C. void *è un puntatore a qualsiasi cosa . È possibile utilizzare void *invece di id, ma non è consigliabile perché non si riceveranno mai avvisi del compilatore per nulla.

Si consiglia di vedere 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
Non proprio. (void *) le variabili digitate non possono essere affatto l'obiettivo delle invocazioni del metodo. Il risultato è "avvertimento: tipo di destinatario non valido 'void *'" dal compilatore.
bbum

@bbum: le void *variabili digitate sicuramente possono essere il bersaglio delle invocazioni del metodo: è un avvertimento, non un errore. Non solo, si può fare questo: int i = (int)@"Hello, string!";e follow-up con: printf("Sending to an int: '%s'\n", [i UTF8String]);. È un avvertimento, non un errore (e non esattamente raccomandato, né portatile). Ma il motivo per cui puoi fare queste cose è tutto di base C.
johne,

1
Scusate. Hai ragione; è un avvertimento, non un errore. Considero solo gli avvisi come errori sempre e ovunque.
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;

Il codice sopra riportato proviene da objc.h, quindi sembra che id sia un'istanza di objc_object struct e che un puntatore isa possa legarsi con qualsiasi oggetto di Classe C Obiettivo, mentre void * è solo un puntatore non tipizzato.


2

La mia comprensione è che id rappresenta un puntatore a un oggetto mentre void * può puntare a qualsiasi cosa in realtà, purché lo si cast sul tipo in cui si desidera utilizzarlo


Se stai eseguendo il casting da (void *) a un tipo di oggetto, incluso id, probabilmente stai sbagliando. Ci sono motivi per farlo, ma sono pochi, lontani tra loro e quasi sempre indicativi di un difetto di progettazione.
bbum

1
citare "ci sono ragioni per farlo, ma sono pochi, lontani tra" vero. Dipende dalla situazione. Comunque non farei una dichiarazione generale come "probabilmente stai sbagliando" senza un certo contesto.
hhafez,

Vorrei fare una dichiarazione generale; ho dovuto dare la caccia e correggere troppi dannati bug a causa del lancio sul tipo sbagliato con vuoto * in mezzo. L'unica eccezione sono le API basate su callback che accettano un argomento di contesto void * il cui contratto stabilisce che il contesto rimarrà intatto tra l'impostazione del callback e la ricezione del callback.
bbum

0

Oltre a quanto già detto, esiste una differenza tra oggetti e puntatori relativi alle raccolte. Ad esempio, se si desidera inserire qualcosa in NSArray, è necessario un oggetto (di tipo "id") e non è possibile utilizzare un puntatore di dati non elaborati (di tipo "void *"). È possibile utilizzare [NSValue valueWithPointer:rawData]per convertire void *rawDdatanel tipo "id" per utilizzarlo all'interno di una raccolta. In generale "id" è più flessibile e ha più semantica relativa agli oggetti ad esso associati. Ci sono altri esempi che spiegano il tipo id di Objective C qui .

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.