Ziel-C: Unterschied zwischen id und void *

Antworten:

240

void * bedeutet "ein Verweis auf einen zufälligen Speicherblock mit untypisiertem / unbekanntem Inhalt"

id bedeutet "eine Referenz auf ein zufälliges Objective-C-Objekt unbekannter Klasse"

Es gibt zusätzliche semantische Unterschiede:

  • Im Modus "Nur GC" oder "GC-unterstützt" gibt der Compiler Schreibbarrieren für Typreferenzen aus id, jedoch nicht für Typreferenzen void *. Bei der Deklaration von Strukturen kann dies ein kritischer Unterschied sein. Das Deklarieren von iVars like void *_superPrivateDoNotTouch;führt zu vorzeitigem Ernten von Objekten, wenn _superPrivateDoNotTouches sich tatsächlich um ein Objekt handelt. Tu das nicht.

  • Wenn Sie versuchen, eine Methode für eine Referenz des void *Typs aufzurufen, wird eine Compiler-Warnung angezeigt.

  • Der Versuch, eine Methode für einen idTyp aufzurufen, warnt nur, wenn die aufgerufene Methode in keiner der @interfacevom Compiler angezeigten Deklarationen deklariert wurde .

Daher sollte man ein Objekt niemals als ein Objekt bezeichnen void *. Ebenso sollte vermieden werden, eine idtypisierte Variable zu verwenden, um auf ein Objekt zu verweisen. Verwenden Sie die spezifischste klassentypisierte Referenz, die Sie können. Auch NSObject *ist besser alsid weil der Compiler zumindest eine bessere Validierung von Methodenaufrufen anhand dieser Referenz bereitstellen kann.

Die eine übliche und gültige Verwendung von void * ist eine undurchsichtige Datenreferenz, die über eine andere API übertragen wird.

Betrachten Sie die sortedArrayUsingFunction: context:Methode von NSArray:

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

Die Sortierfunktion würde wie folgt deklariert:

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

In diesem Fall übergibt der NSArray lediglich alles, was Sie als contextArgument an die Methode übergeben, alscontext Argument. Für NSArray handelt es sich um einen undurchsichtigen Teil von Daten in Zeigergröße, und Sie können sie für jeden gewünschten Zweck verwenden.

Ohne ein Abschluss-Feature in der Sprache ist dies die einzige Möglichkeit, einen Datenblock mit einer Funktion mitzunehmen. Beispiel; Wenn Sie möchten, dass mySortFunc () bedingt nach Groß- und Kleinschreibung oder ohne Berücksichtigung der Groß- und Kleinschreibung sortiert, während Sie dennoch threadsicher sind, würden Sie den Indikator, bei dem zwischen Groß- und Kleinschreibung unterschieden wird, im Kontext übergeben und wahrscheinlich auf dem Weg nach innen und nach außen wirken.

Zerbrechlich und fehleranfällig, aber der einzige Weg.

Blöcke lösen dieses Problem - Blöcke sind Verschlüsse für C. Sie sind in Clang - http://llvm.org/ verfügbar und in Snow Leopard ( http://developer.apple.com/library/ios/documentation/Performance) allgegenwärtig /Reference/GCD_libdispatch_Ref/GCD_libdispatch_Ref.pdf ).

bbum
quelle
Außerdem bin ich mir ziemlich sicher, dass idangenommen wird, dass a auf -retainund reagiert -release, während a void*für den Angerufenen völlig undurchsichtig ist. Sie können keinen beliebigen Zeiger an übergeben -performSelector:withObject:afterDelay:(das Objekt bleibt erhalten), und Sie können nicht davon ausgehen, dass +[UIView beginAnimations:context:]der Kontext erhalten bleibt (der Animationsdelegierte sollte das Eigentum am Kontext behalten; UIKit behält den Animationsdelegierten).
tc.
3
Es können keine Annahmen darüber getroffen werden, worauf idreagiert wird. Ein idkann leicht auf eine Instanz einer Klasse verweisen, die nicht inhärent ist NSObject. In der Praxis passt Ihre Aussage jedoch am besten zum Verhalten in der realen Welt. Sie können nicht <NSObject>implementierende Klassen nicht mit der Foundation API mischen und kommen sehr weit, das ist definitiv sicher!
bbum
2
Jetzt idund ClassTypen werden unter ARC als Zeiger für beibehaltene Objekte behandelt . Die Annahme trifft also zumindest unter ARC zu.
Eonil
Was ist "vorzeitiges Ernten"?
Brad Thomas
1
@BradThomas Wenn ein Garbage Collector Speicher sammelt, bevor das Programm damit fertig ist.
bbum
21

id ist ein Zeiger auf ein objektives C-Objekt, wobei void * ein Zeiger auf irgendetwas ist.

id deaktiviert auch Warnungen im Zusammenhang mit dem Aufrufen unbekannter Methoden, z. B.:

[(id)obj doSomethingWeirdYouveNeverHeardOf];

gibt nicht die übliche Warnung vor unbekannten Methoden. Es wird natürlich zur Laufzeit eine Ausnahme auslösen, es sei denn, obj ist null oder implementiert diese Methode tatsächlich.

Oft sollten Sie verwenden NSObject*oder id<NSObject>bevorzugen id, was zumindest bestätigt, dass das zurückgegebene Objekt ein Kakao-Objekt ist, sodass Sie sicher Methoden wie Beibehalten / Freigeben / Autorelease verwenden können.

Peter N Lewis
quelle
2
Bei Methodenaufrufen generiert ein Ziel vom Typ (ID) eine Warnung, wenn die Zielmethode nirgendwo deklariert wurde. In Ihrem Beispiel hätte doSomethingWeirdYouveNeverHeardOf irgendwo deklariert werden müssen, damit keine Warnung angezeigt wird.
bbum
Sie haben Recht, ein besseres Beispiel wäre so etwas wie storagePolicy.
Peter N Lewis
@PeterNLewis Ich bin nicht einverstanden Often you should use NSObject*statt id. Mit der Angabe NSObject*sagen Sie tatsächlich explizit, dass das Objekt ein NSObject ist. Jeder Methodenaufruf an das Objekt führt zu einer Warnung, jedoch zu keiner Laufzeitausnahme, solange dieses Objekt tatsächlich auf den Methodenaufruf reagiert. Die Warnung ist offensichtlich ärgerlich, also idist es besser. Grob gesagt können Sie dann genauer sagen id<MKAnnotation>, indem Sie beispielsweise sagen , was in diesem Fall bedeutet, dass das Objekt, unabhängig vom Objekt, dem MKAnnotation-Protokoll entsprechen muss.
Pnizzle
1
Wenn Sie die ID <MKAnnotation> verwenden möchten, können Sie auch NSObject <MKAnnotation> * verwenden. Auf diese Weise können Sie eine der Methoden in der MKAnnotation und eine der Methoden in NSObject verwenden (dh alle Objekte in der normalen NSObject-Stammklassenhierarchie) und eine Warnung für alles andere erhalten, was viel besser ist als keine Warnung und ein Laufzeitabsturz.
Peter N Lewis
8

Wenn eine Methode einen Rückgabetyp von idhat, können Sie jedes Objective-C-Objekt zurückgeben.

void bedeutet, dass die Methode nichts zurückgibt.

void *ist nur ein Zeiger. Sie können den Inhalt der Adresse, auf die der Zeiger zeigt, nicht bearbeiten.

fphilipe
quelle
2
Da es für den Rückgabewert einer Methode gilt, meistens richtig. Wie es für die Deklaration von Variablen oder Argumenten gilt, nicht ganz. Und Sie können jederzeit ein (void *) in einen spezifischeren Typ umwandeln, wenn Sie den Inhalt lesen / schreiben möchten - nicht, dass dies eine gute Idee ist.
bbum
8

idist ein Zeiger auf ein Objective-C-Objekt. void *ist ein Zeiger auf alles . Sie könnten void *anstelle von verwendenid , aber es wird nicht empfohlen, da Sie niemals Compiler-Warnungen für irgendetwas erhalten würden.

Möglicherweise möchten Sie stackoverflow.com/questions/466777/whats-the-difference-bet-clare-a-variable-id-and-nsobject und unixjunkie.blogspot.com/2008/03/id-vs-nsobject-vs anzeigen -id.html .

Michael
quelle
1
Nicht ganz. (void *) typisierte Variablen können überhaupt nicht das Ziel von Methodenaufrufen sein. Dies führt zu "Warnung: ungültiger Empfängertyp 'void *'" vom Compiler.
bbum
@bbum: Typisierte void *Variablen können definitiv das Ziel von Methodenaufrufen sein - es ist eine Warnung, kein Fehler. Nicht nur das, Sie können dies tun: int i = (int)@"Hello, string!";und folgen Sie mit : printf("Sending to an int: '%s'\n", [i UTF8String]);. Es ist eine Warnung, kein Fehler (und nicht genau empfohlen oder tragbar). Aber der Grund, warum Sie diese Dinge tun können, ist alles grundlegende C.
Johne
1
Es tut uns leid. Du hast Recht; Es ist eine Warnung, kein Fehler. Ich behandle Warnungen immer und überall als Fehler.
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;

Der obige Code stammt von objc.h, es sieht also so aus, als ob id eine Instanz der Struktur objc_object ist und ein Zeiger an jedes Objekt der Objective C-Klasse gebunden werden kann, während void * nur ein untypisierter Zeiger ist.

Jack
quelle
2

Mein Verständnis ist, dass id einen Zeiger auf ein Objekt darstellt, während void * wirklich auf alles verweisen kann, solange Sie es dann in den Typ umwandeln, als den Sie es verwenden möchten

hhafez
quelle
Wenn Sie von (void *) in einen Objekttyp, einschließlich id, umwandeln, machen Sie das höchstwahrscheinlich falsch. Es gibt Gründe dafür, aber es gibt nur wenige, die weit voneinander entfernt sind und fast immer auf einen Konstruktionsfehler hinweisen.
bbum
1
Zitat "Es gibt Gründe dafür, aber es gibt nur wenige, weit voneinander entfernt" stimmt das. Es hängt von der Situation ab. Ich würde jedoch keine pauschale Aussage wie "Sie machen es sehr wahrscheinlich falsch" ohne irgendeinen Kontext machen.
Hhafez
Ich würde eine pauschale Erklärung abgeben; musste zu viele verdammte Bugs jagen und beheben, weil sie auf den falschen Typ geworfen wurden, mit Leere * dazwischen. Die einzige Ausnahme sind Callback-basierte APIs, die ein void * -Kontextargument verwenden, dessen Vertrag besagt, dass der Kontext zwischen dem Einrichten des Rückrufs und dem Empfangen des Rückrufs unberührt bleibt.
bbum
0

Zusätzlich zu dem, was bereits gesagt wurde, gibt es einen Unterschied zwischen Objekten und Zeigern, die sich auf Sammlungen beziehen. Wenn Sie beispielsweise etwas in NSArray einfügen möchten, benötigen Sie ein Objekt (vom Typ "id") und können dort keinen Rohdatenzeiger (vom Typ "void *") verwenden. Sie können den Typ "id" [NSValue valueWithPointer:rawData]konvertieren void *rawDdata, um ihn in einer Sammlung zu verwenden. Im Allgemeinen ist "id" flexibler und hat mehr Semantik in Bezug auf Objekte, die damit verbunden sind. Weitere Beispiele zur Erläuterung des ID-Typs von Ziel C finden Sie hier .

Battlmonstr
quelle