Objective-C: різниця між id і void *


Відповіді:


240

void * означає "посилання на деякий випадковий фрагмент пам'яті з нетиповим / невідомим вмістом"

id означає "посилання на якийсь випадковий об'єкт Objective-C невідомого класу"

Є додаткові смислові відмінності:

  • У режимах GC Only або GC, що підтримуються, компілятор видасть бар'єри запису для посилань типу id, але не для типу void *. При декларуванні структур це може бути критичною різницею. Оголошення iVars як подібне void *_superPrivateDoNotTouch;призведе до передчасного пожвавлення об'єктів, якщо _superPrivateDoNotTouchвін є об'єктом. Не робіть цього.

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

  • спроба виклику методу на idтип попередить лише тоді, коли викликаний метод не був оголошений ні в одній з @interfaceдекларацій, які бачив компілятор.

Таким чином, ніколи не слід називати об’єкт як a 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 ).


Крім того, я впевнений, що idвважається, що відповідь на, -retainа -release, а, void*є абсолютно непрозорою для кальє. Ви не можете передати довільний покажчик на -performSelector:withObject:afterDelay:(він зберігає об'єкт), і ви не можете припустити, що +[UIView beginAnimations:context:]він збереже контекст (делегат анімації повинен зберігати право власності на контекст; UIKit зберігає делегата анімації).
тс.

3
Не можна робити жодних припущень щодо того, на що idвідповідає. idМоже легко звернутися до екземпляру класу , який не є притаманним від NSObject. Однак, кажучи практично, ваше твердження найкраще відповідає поведінці реального світу; Ви не можете змішати <NSObject>класи, що не реалізуються, з API API та отримати дуже далеко, це точно!
bbum

2
Тепер idі Classтипи трактуються як вказівний об'єкт, що можна отримати в ARC. Тож припущення вірно принаймні за ARC.
eonil

Що таке "передчасне пожинання"?
Бред Томас

1
@BradThomas Коли збирач сміття збирає пам'ять, перш ніж програма буде виконана з ним.
bbum

21

id - вказівник на об'єктивний об'єкт C, де as void * - вказівник на що-небудь.

id також вимикає попередження, пов’язані з викликом невідомих mthods, наприклад:

[(id)obj doSomethingWeirdYouveNeverHeardOf];

не дасть звичайного попередження про невідомі методи. Звичайно, це призведе до виключення під час виконання, якщо obj не дорівнює чи дійсно не реалізує цей метод.

Часто вам слід використовувати NSObject*або id<NSObject>віддавати перевагу тому id, що хоча б підтверджує, що повернутий об’єкт є об'єктом какао, тому ви можете сміливо використовувати такі методи, як утримувати / випускати / автовипускати на ньому.


2
Для викликів методу ціль типу (id) генерує попередження, якщо цільовий метод ніде не оголошено. Таким чином, у вашому прикладі потрібно було десь оголосити doSomethingWeirdYouveNeverHeardOf, щоб не було попередження.
bbum

Ти маєш рацію, кращим прикладом може бути щось на зразок сховищаPolicy.
Пітер N Льюїс

@PeterNLewis Я не згоден, Often you should use NSObject*а не id. Вказуючи, NSObject*ви насправді прямо говорите, що об'єкт є NSObject. Будь-який виклик методу до об'єкта призведе до попередження, але не буде винятком виконання, поки цей об'єкт дійсно відповідає на виклик методу. Попередження, очевидно, дратує, так що idкраще. Тоді ви можете бути більш конкретними, наприклад, сказавши id<MKAnnotation>, що в цьому випадку означає будь-який об'єкт, він повинен відповідати протоколу MKAnnotation.
pnizzle

1
Якщо ви збираєтеся використовувати ідентифікатор <MKAnnotation>, ви також можете також використовувати NSObject <MKAnnotation> *. Потім це дозволяє використовувати будь-який з методів у MKAnnotation та будь-який із методів у NSObject (тобто, всі об’єкти у звичайній ієрархії кореневих класів NSObject) та отримувати попередження про все інше, що набагато краще, ніж відсутність попередження та аварія виконання.
Пітер Н Льюїс

8

Якщо метод має тип повернення, idви можете повернути будь-який об’єкт Objective-C.

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

void *є лише вказівником. Ви не зможете редагувати вміст за адресою, на яку вказує вказівник.


2
Оскільки це стосується зворотного значення методу, здебільшого вірно. Як це стосується декларування змінних чи аргументів, не зовсім. І ви завжди можете передавати (пустоту *) на більш конкретний тип, якщо хочете прочитати / записати вміст - не те, що це добре робити.
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]);. Це попередження, а не помилка (і не точно рекомендується, ні переноситься). Але причина, чому ти можеш це робити, - це все основна С.
johne

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 * може вказувати на що-небудь дійсно, доки ви потім приведете його до типу, який ви хочете використовувати як


Якщо ви передаваєте (void *) на якийсь тип об'єкта, включаючи id, ви, ймовірно, робите це неправильно. Для цього є причини, але їх небагато, далеко між, і майже завжди вказують на недолік дизайну.
bbum

1
цитуйте "для цього є причини, але їх небагато, далеко", правда, це. Це залежить від ситуації. Однак я б не робив бланкетного твердження на кшталт "ти, швидше за все, робиш це неправильно", з деяким контекстом.
hhafez

Я б зробив ковдру заяву; довелося шукати і виправляти занадто багато проклятих помилок через те, що вони перейшли у неправильний тип з пустотою * між ними. Єдиним винятком є ​​API на основі зворотного виклику, які приймають аргумент контексту void *, у договорі якого зазначено, що контекст залишатиметься недоторканим між налаштуванням зворотного дзвінка та отриманням зворотного дзвінка.
bbum

0

Крім того, що вже було сказано, існує різниця між об'єктами та покажчиками, що стосуються колекцій. Наприклад, якщо ви хочете щось поставити в NSArray, вам потрібен об'єкт (типу "id"), і ви не можете використовувати там необроблений покажчик даних (типу "void *"). Ви можете використовувати [NSValue valueWithPointer:rawData]для перетворення void *rawDdataв тип "id", щоб використовувати його всередині колекції. Загалом "id" є більш гнучким і має більше семантики, пов'язаної з приєднаними до нього об'єктами. Там ще приклади , що пояснюють тип документа з Objective C тут .

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.