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.