Цель-C: разница между id и void *

Ответы:

240

void * означает «ссылка на некоторый случайный фрагмент памяти с нетипизированным / неизвестным содержимым»

id означает «ссылка на некоторый случайный объект Objective-C неизвестного класса»

Есть дополнительные семантические различия:

  • В режимах GC Only или GC Supported компилятор будет создавать барьеры записи для ссылок типа id, но не для типа void *. При объявлении структур это может быть критическим различием. Объявление типа iVars void *_superPrivateDoNotTouch;приведет к преждевременному пожатию объектов, если _superPrivateDoNotTouchэто действительно объект. Не делай этого.

  • попытка вызвать метод для ссылки void *типа вызовет предупреждение компилятора.

  • попытка вызвать метод для idтипа будет предупреждать только в том случае, если вызываемый метод не был объявлен ни в одном из @interfaceобъявлений, видимых компилятором.

Таким образом, никогда не следует ссылаться на объект как на void *. Точно так же следует избегать использования idтипизированной переменной для ссылки на объект. Используйте наиболее конкретную типизированную ссылку класса, которую вы можете. Даже NSObject *лучше, чем idпотому, что компилятор может, по крайней мере, обеспечить лучшую проверку вызовов методов по этой ссылке.

Единственное распространенное и правильное использование void *- это непрозрачная ссылка на данные, которая передается через какой-то другой API.

Рассмотрим sortedArrayUsingFunction: context:метод NSArray:

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

Функция сортировки будет объявлена ​​как:

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

В этом случае NSArray просто передает все, что вы передаете в качестве contextаргумента, в метод в качестве contextаргумента. Это непрозрачный кусок данных размером с указатель, что касается NSArray, и вы можете использовать его для любых целей.

Без функции типа замыкания в языке это единственный способ перенести часть данных с функцией. Пример; если вы хотите, чтобы mySortFunc () выполняла условную сортировку как чувствительную к регистру или нечувствительную к регистру, так и в то же время сохраняющую потокобезопасность, вы бы передавали индикатор с учетом регистра в контексте, что, вероятно, приводит к входу и выходу.

Хрупкие и подверженные ошибкам, но единственный способ.

Блоки решают эту проблему - блоки являются замыканиями для C. Они доступны в Clang - http://llvm.org/ и распространены в Snow Leopard ( http://developer.apple.com/library/ios/documentation/Performance /Reference/GCD_libdispatch_Ref/GCD_libdispatch_Ref.pdf ).

bbum
источник
Кроме того, я уверен, что idпредполагается , что an отвечает на -retainи -release, тогда как a void*совершенно непрозрачен для вызываемого. Вы не можете передать произвольный указатель на -performSelector:withObject:afterDelay:(он сохраняет объект), и вы не можете предполагать, что +[UIView beginAnimations:context:]он сохранит контекст (делегат анимации должен сохранять владение контекстом; UIKit сохраняет делегат анимации).
тк.
3
Никакие предположения не могут быть сделаны о том, что idотвечает. idМожет легко обратиться к экземпляру класса , который не присущ от NSObject. Тем не менее, на практике ваше утверждение лучше всего соответствует поведению в реальном мире; Вы не можете смешивать не <NSObject>реализующие классы с Foundation API и далеко продвинуться, это точно!
bbum 13.10.10
2
Теперь idи Classтипы обрабатываются как указатель сохраняемого объекта в ARC. Таким образом, предположение верно, по крайней мере, при ARC.
eonil
Что такое "преждевременное пожатие"?
Брэд Томас
1
@BradThomas Когда сборщик мусора собирает память до того, как программа завершит работу с ней.
Бум
21

id - это указатель на целевой объект C, где void * - указатель на что-либо.

id также отключает предупреждения, связанные с вызовом неизвестных методов, например:

[(id)obj doSomethingWeirdYouveNeverHeardOf];

не даст обычного предупреждения о неизвестных методах. Это, конечно, вызовет исключение во время выполнения, если obj не равен nil или действительно не реализует этот метод.

Часто вы должны использовать NSObject*или id<NSObject>в предпочтении id, что по крайней мере подтверждает, что возвращаемый объект является объектом Какао, поэтому вы можете безопасно использовать такие методы, как retain / release / autorelease для него.

Питер Н Льюис
источник
2
Для вызовов методов цель типа (id) будет генерировать предупреждение, если целевой метод нигде не был объявлен. Таким образом, в вашем примере doSomethingWeirdYouveNeverHeardOf должен был быть объявлен где-то, чтобы не было предупреждения.
бум
Вы правы, лучшим примером будет что-то вроде storagePolicy.
Питер Н Льюис
@PeterNLewis я не согласен, Often you should use NSObject*а не id. Указывая, NSObject*вы на самом деле явно говорите, что объект является NSObject. Любой вызов метода для объекта вызовет предупреждение, но не исключение времени выполнения, если этот объект действительно отвечает на вызов метода. Предупреждение явно раздражает, тем idлучше. Конечно, вы можете быть более точными, например, говоря id<MKAnnotation>, что в данном случае означает, что объект должен соответствовать протоколу MKAnnotation.
pnizzle
1
Если вы собираетесь использовать id <MKAnnotation>, то вы также можете использовать NSObject <MKAnnotation> *. Который затем позволяет вам использовать любой из методов в MKAnnotation и любой из методов в NSObject (т. Е. Все объекты в нормальной иерархии корневых классов NSObject), и получать предупреждение для всего остального, что гораздо лучше, чем отсутствие предупреждения и сбой во время выполнения.
Питер Н Льюис
8

Если у метода есть тип возврата, idвы можете вернуть любой объект Objective-C.

void значит, метод ничего не вернет.

void *это просто указатель. Вы не сможете редактировать содержимое по адресу, на который указывает указатель.

fphilipe
источник
2
Поскольку это относится к возвращаемому значению метода, в основном верно. Что касается объявления переменных или аргументов, то не совсем. И вы всегда можете привести (void *) к более конкретному типу, если вы хотите читать / писать содержимое - не то, чтобы это было хорошей идеей.
bbum
8

idуказатель на объект Objective-C void *это указатель на что-нибудь . Вы можете использовать void *вместо id, но это не рекомендуется, потому что вы никогда не получите предупреждения компилятора ни за что.

Возможно, вы захотите увидеть stackoverflow.com/questions/466777/whats-the-difference-between-declaring-a-variable-id-and-nsobject и unixjunkie.blogspot.com/2008/03/id-vs-nsobject-vs -id.html .

Майкл
источник
1
Не совсем. (void *) типизированные переменные вообще не могут быть целью вызовов методов. Это приводит к «предупреждению: недопустимый тип получателя« void * »» от компилятора.
bbum
@bbum: void * типизированные переменные определенно могут быть целью вызовов методов - это предупреждение, а не ошибка. Мало того, что вы можете сделать это: int i = (int)@"Hello, string!";и следовать с: printf("Sending to an int: '%s'\n", [i UTF8String]);. Это предупреждение, а не ошибка (и не совсем рекомендуемое, ни переносимое). Но причина, по которой вы можете делать эти вещи, заключается в
простоте
1
Сожалею. Ты прав; это предупреждение, а не ошибка. Я просто воспринимаю предупреждения как ошибки всегда и везде.
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;

Вышеприведенный код взят из objc.h, поэтому похоже, что id является экземпляром структуры objc_object, и указатель isa может связываться с любым объектом класса Objective C, а void * - просто нетипизированный указатель.

разъем
источник
2

Насколько я понимаю, id представляет собой указатель на объект, в то время как void * может указывать на что-либо действительно, при условии, что вы приведете его к типу, который вы хотите использовать как

hhafez
источник
Если вы преобразуете из (void *) в какой-либо тип объекта, включая id, вы, скорее всего, делаете это неправильно. Есть причины для этого, но они немногочисленны и почти всегда свидетельствуют о недостатке дизайна.
bbum
1
цитата "Есть причины для этого, но их мало, далеко" правда. Это зависит от ситуации. Однако я не стал бы делать общее заявление типа «вы, скорее всего, делаете это неправильно» без какого-либо контекста.
Hhafez
Я сделал бы общее заявление; пришлось выследить и исправить слишком много проклятых ошибок из-за приведения к неправильному типу с пустым * между ними. Единственным исключением являются основанные на обратном вызове API, которые принимают аргумент void * context, в контракте которого говорится, что контекст останется нетронутым между настройкой обратного вызова и получением обратного вызова.
bbum
0

В дополнение к тому, что уже сказано, есть различие между объектами и указателями, связанными с коллекциями. Например, если вы хотите поместить что-то в NSArray, вам нужен объект (типа "id"), и вы не можете использовать там указатель необработанных данных (типа "void *"). Вы можете использовать [NSValue valueWithPointer:rawData]для преобразования void *rawDdataв тип "id" для использования его внутри коллекции. В общем, «id» более гибок и имеет больше семантики, связанной с объектами, прикрепленными к нему. Есть больше примеров, объясняющих тип идентификатора Objective C здесь .

battlmonstr
источник