Надіслати повідомлення до нуля в Objective-C


107

Як розробник Java, який читає документацію Apple на Objective-C 2.0: мені цікаво, що означає " відправка повідомлення на нуль " - не кажучи вже про те, наскільки це насправді корисно. Витяг із документації:

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

  • Якщо метод повертає об'єкт, будь-який тип вказівника, будь-який цілочисельний скаляр розміром, менший або рівний sizeof (пустота *), поплавок, подвійний, довгий подвійний або довгий довгий, то повідомлення, надіслане нулю, повертає 0 .
  • Якщо метод повертає структуру, визначену в Mac OS X ABI Функціональний посібник виклику для повернення в регістри, то повідомлення, що надсилається на нуль, повертає 0,0 для кожного поля в структурі даних. Інші типи даних структури не заповнюватимуться нулями.
  • Якщо метод повертає щось інше, ніж вищезгадані типи значень, значення повернення повідомлення, надісланого nil, не визначене.

Чи зробила Ява мого мозку нездатним поглибити пояснення вище? Або є щось, чого мені не вистачає, що зробило б це так ясно, як скло?

Я розумію, що повідомлення / приймачі в «Objective-C», я просто заплутаний у зв'язку з приймачем nil.


2
Я також мав передумови Java, і мене спочатку жахнулася ця приємна особливість, але зараз я вважаю це абсолютно ЛЮБЛІ!
Валентин Раду

1
Дякую, це чудове запитання. Ви бачили, щоб побачити переваги цього? Мене це вражає як річ "не помилка, особливість". Я продовжую отримувати помилки, де Java просто ляпає мене за винятком, тому я знав, де проблема. Я не радий торгувати виключенням нульового вказівника, щоб зберегти рядок чи два тривіального коду тут і там.
Maciej Trybiło

Відповіді:


92

Ну, я думаю, це можна описати, використовуючи дуже надуманий приклад. Скажімо, у вас є метод на Java, який роздруковує всі елементи в ArrayList:

void foo(ArrayList list)
{
    for(int i = 0; i < list.size(); ++i){
        System.out.println(list.get(i).toString());
    }
}

Тепер, якщо ви називаєте такий метод так: someObject.foo (NULL); ви, ймовірно, отримаєте NullPointerException, коли він намагатиметься отримати доступ до списку, у цьому випадку у виклику to list.size (); Тепер ви, ймовірно, ніколи не називатимете someObject.foo (NULL) зі значенням NULL таким. Однак, можливо, ви отримали свій ArrayList з методу, який повертає NULL, якщо він стикається з деякою помилкою, що генерує ArrayList, як someObject.foo (otherObject.getArrayList ());

Звичайно, у вас також будуть проблеми, якщо ви зробите щось подібне:

ArrayList list = NULL;
list.size();

Тепер у Objective-C ми маємо еквівалентний метод:

- (void)foo:(NSArray*)anArray
{
    int i;
    for(i = 0; i < [anArray count]; ++i){
        NSLog(@"%@", [[anArray objectAtIndex:i] stringValue];
    }
}

Тепер, якщо у нас є такий код:

[someObject foo:nil];

у нас така ж ситуація, коли Java виробляє NullPointerException. Об'єкт nil буде доступний спочатку під час [anArray count] Однак, замість того, щоб викидати NullPointerException, Objective-C просто поверне 0 у відповідності з наведеними вище правилами, тому цикл не запуститься. Однак якщо ми встановимо цикл для виконання заданої кількості разів, то спочатку ми надсилаємо повідомлення anArray за адресою [anArray objectAtIndex: i]; Це також поверне 0, але оскільки objectAtIndex: повертає вказівник, а вказівник на 0 - нуль / NULL, NSLog буде передаватися нуль кожен раз через цикл. (Хоча NSLog є функцією, а не методом, він виводить (null), якщо передав нульовий NSString.

У деяких випадках приємніше мати NullPointerException, оскільки ви можете відразу сказати, що з програмою щось не так, але якщо ви не виберете виняток, програма вийде з ладу. (У C намагання знеструмлювати NULL таким чином спричиняє збій програми.) В Objective-C він натомість викликає, можливо, неправильну поведінку під час виконання. Однак якщо у вас є метод, який не порушується, якщо він повертає 0 / nil / NULL / нульову структуру, то це позбавить вас від необхідності перевіряти, щоб переконатися, що об'єкт або параметри є нульовими.


33
Напевно, варто згадати, що ця поведінка була предметом багато дискусій у спільноті Objective-C протягом останніх кількох десятиліть. Компроміс між "безпекою" та "зручністю" різними оцінюється різними людьми.
Марк Бессі

3
На практиці існує велика симетрія між передачею повідомлень на нуль і тим, як працює «Objective-C», особливо в нових слабких покажчиках, що функціонують в ARC. Слабкі вказівники автоматично нулюються. Тож спроектуйте API, щоб він міг відповідати 0 / нуль / NIL / NULL тощо.
Cthutu

1
Я думаю, що якщо ви myObject->iVarце зробите, то вийде з ладу, незалежно від того, чи це C з об'єктами чи без них . (вибачте за могилу.)
11684

3
@ 11684 Це правильно, але ->це вже не операція Objective-C, а дуже загальний C-ізм.
bbum

1
Недавній корінь OSX експлуатує / прихований бекдор апі доступний для всіх користувачів ( а не тільки адміни) з - за Obj-C в Nil повідомлень.
цві


41

Усі інші пости є правильними, але, можливо, тут важлива концепція.

У викликах методу Objective-C будь-яка посилання на об'єкт, яка може прийняти селектор, є дійсною ціллю для цього селектора.

Це економить багато "є цільовим об'єктом типу X?" код - поки приймаючий об'єкт реалізує селектор, він абсолютно не має ніякої різниці, що це за клас! nilє NSObject, який приймає будь-який селектор - він просто нічого не робить . Це виключає безліч код "перевірка на нуль, не надсилайте повідомлення, якщо це правда". (Концепція "якщо вона приймає її, вона реалізує її" - це також те, що дозволяє створювати протоколи , подібні до інтерфейсів Java: декларація, що якщо клас реалізує заявлені методи, то він відповідає протоколу.)

Причиною цього є усунення коду мавпи, який нічого не робить, окрім як підтримує компілятор. Так, ви отримуєте накладні витрати на ще один виклик методу, але ви економите час програміста , що набагато дорожчий ресурс, ніж час процесора. Крім того, ви усуваєте більше коду та більше умовної складності зі своєї програми.

Роз'яснення для речей: ви можете подумати, що це не гарний шлях, але мова про те, як реалізується мова, і це рекомендована ідіома програмування в Objective-C (див. Лекції програмування Stanford iPhone).


17

Це означає, що час виконання не видає помилки, коли objc_msgSend викликається на нульовому покажчику; натомість воно повертає деяке (часто корисне) значення. Повідомлення, які можуть мати побічний ефект, нічого не роблять.

Це корисно, оскільки більшість значень за замовчуванням є більш доречними, ніж помилка. Наприклад:

[someNullNSArrayReference count] => 0

Тобто, нуль виявляється порожнім масивом. Приховування нульової посилання NSView нічого не робить. Зручно, так?


12

У цитаті з документації є два окремих поняття - можливо, це буде краще, якби документація зробила це більш зрозумілим:

У какао є кілька моделей, які використовують цей факт.

Значення, повернене з повідомлення на нуль, також може бути дійсним:

Тут, мабуть, більш актуально: тут, як правило, можливість надсилати повідомлення, щоб nilзробити код більш простим - вам не доведеться всюди перевіряти нульові значення. Канонічний приклад - це, мабуть, метод аксесуара:

- (void)setValue:(MyClass *)newValue {
    if (value != newValue) { 
        [value release];
        value = [newValue retain];
    }
}

При відправці повідомлення nilбули дійсні, цей метод буде більш складним - Ви повинні були б мати дві додаткові перевірки для забезпечення valueі newValueНЕ nilперед відправкою їм повідомлення.

Остання точка (що значення, повернуті з повідомлення nil, також зазвичай є дійсними), однак додає ефекту мультиплікатора до першого. Наприклад:

if ([myArray count] > 0) {
    // do something...
}

Цей код знову не потребує перевірки nil значень, а надходить природним чином ...

Все це говорило, що додаткова гнучкість, до якої можна надсилати повідомлення, nilдійсно приходить за певну ціну. Є ймовірність, що ви на якомусь етапі випишете код, який виходить з ладу своєрідним чином, оскільки ви не врахували можливість того, що може бути значенням nil.


12

Від Greg Parker «S сайті :

Якщо використовується LLVM Compiler 3.0 (Xcode 4.2) або новішої версії

Повідомлення до нуля із типом повернення | повернення
Цілі числа до 64 біт | 0
Плаваюча точка до довгого подвійного | 0,0
Покажчики | нуль
Конструкції | {0}
Будь-який тип _Complex | {0, 0}

9

Це означає, що часто не потрібно перевіряти наявність нульових об'єктів скрізь для безпеки - зокрема:

[someVariable release];

або, як зазначалося, різні методи підрахунку та довжини всі повертають 0, коли у вас є нульове значення, тому вам не доведеться додавати додаткові чеки на нуль у всьому:

if ( [myString length] > 0 )

або це:

return [myArray count]; // say for number of rows in a table

Майте на увазі, що інша сторона монети - це потенціал для помилок, таких як "if ([myString length] == 1)"
hatfinch

Як це помилка? [довжина myString] повертає нуль (нуль), якщо myString - нуль ... одна річ, на мою думку, може бути проблемою, є [myView frame], який, на мою думку, може дати вам щось нерозумне, якщо myView є нульовим.
Kendall Helmstetter Gelner

Якщо ви розробляєте свої класи та методи навколо концепції, що значення за замовчуванням (0, нуль, NO) означають "не корисно", це потужний інструмент. Мені ніколи не доводиться перевіряти рядки на нуль, перш ніж перевіряти довжину. Для мене порожній рядок є настільки ж марним і нульовим рядком, коли я обробляю текст. Я також розробник Java, і я знаю, що пуристисти Java будуть уникати цього, але це економить багато кодування.
Джейсон Фуерстенберг

6

Не думайте про те, що "приймач нульовий"; Я згоден, це досить дивно. Якщо ви надсилаєте повідомлення до нуля, одержувача немає. Ви просто надсилаєте повідомлення ні до чого.

Як з цим боротися - філософська різниця між Java та Objective-C: у Java це помилка; в «Objective-C» - це не-оп.


Існує виняток з такої поведінки в java, якщо ви називаєте статичну функцію на нулі, це еквівалентно виклику функції в часовому класі компіляції змінної (неважливо, чи це нуль).
Роман А. Тейчер

6

Повідомлення ObjC, які надсилаються до нуля і значення яких повернення мають розмір, більший за sizeof (void *), створюють невизначені значення на процесорах PowerPC. На додаток до цього, ці повідомлення призводять до повернення невизначених значень у полях структур, розмір яких перевищує 8 байт і для процесорів Intel. Вінсент Гейбл чудово описав це у своїй публікації в блозі


6

Я не думаю, що жоден з інших відповідей це чітко не згадував: якщо ви звикли до Java, ви повинні пам’ятати, що, хоча у Objective-C на Mac OS X є підтримка обробки обставин, це додаткова мова, яка може бути увімкнено / вимкнено прапором компілятора. Я здогадуюсь, що така конструкція "надсилання повідомлень nilбезпечною" передує включенню підтримки мови для обміну винятками і була зроблена з аналогічною метою: методи можуть повертатися, nilщоб вказувати на помилки, і оскільки відправлення повідомлення nilзазвичай повертаєтьсяnilце, в свою чергу, дозволяє поширювати індикацію помилок через ваш код, тому вам не доведеться перевіряти це на кожному повідомленні. Ви повинні перевірити це лише в тих місцях, де це має значення. Я особисто думаю, що поширення винятків та поводження з ними є кращим способом вирішення цієї мети, але не всі можуть погодитися з цим. (З іншого боку, мені, наприклад, не подобається вимога Java щодо того, щоб ви оголосили, які винятки може викинути метод, що часто змушує вас синтаксично поширювати декларації виключень у всьому коді; але це інша дискусія.)

Я розмістив подібний, але довший відповідь на відповідне запитання "Чи стверджується, що кожне створення об'єкта досягло необхідності в" Цілі C "?" якщо ви хочете більше деталей.


Я ніколи про це не думав. Це здається дуже зручною функцією.
mk12

2
Хороша здогадка, але історично неточна щодо того, чому було прийнято рішення. Обробка винятків існувала в мові з самого початку, хоча оригінальні обробники винятків були досить примітивними порівняно із сучасною ідіомою. Повідомлення Nil-eats було усвідомленим вибором дизайну, похідним від необов'язкової поведінки об'єкта Nil у Smalltalk. Коли були розроблені оригінальні API NeXTSTEP, ланцюжок методів був досить поширеним, і nilповернення часто використовувалося для короткого замикання ланцюга в NO-op.
bbum

2

C не означає нічого як 0 для примітивних значень, а NULL для покажчиків (що еквівалентно 0 в контексті покажчика).

Objective-C будує на представленні С нічого, додаючи нуль. nil - вказівник об'єкта на ніщо. Хоча семантично відрізняються від NULL, вони технічно рівноцінні один одному.

Новоспечені NSO-об'єкти починають життя з вмістом, встановленим 0. Це означає, що всі вказівники, які об'єкт має на інші об'єкти, починаються як нульові, тому, наприклад, не потрібно встановлювати self. (Association) = nil в методах init.

Однак найбільш помітною поведінкою нуля є те, що він може надсилати повідомлення.

В інших мовах, як-от C ++ (або Java), це призведе до краху вашої програми, але в Objective-C, викликаючи метод на nil, повертає нульове значення. Це значно спрощує вирази, оскільки це скасовує необхідність перевірки нуля, перш ніж робити що-небудь:

// For example, this expression...
if (name != nil && [name isEqualToString:@"Steve"]) { ... }

// ...can be simplified to:
if ([name isEqualToString:@"Steve"]) { ... }

Будучи обізнаним про те, як nil працює в Objective-C, ця зручність може бути функцією, а не прихованою помилкою у вашій програмі. Не забудьте захистити від випадків, коли нульові значення небажані, або перевіривши та повернувшись рано, щоб вийти з ладу, або додавши NSParameterAssert, щоб кинути виняток.

Джерело: http://nshipster.com/nil/ https://developer.apple.com/library/ios/#documentation/cocoa/conceptual/objectivec/Chapters/ocObjectsClasses.html (надсилання повідомлення до нуля).

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