Отправка сообщения на ноль в Objective-C

107

Как разработчик Java, читающий документацию Apple по Objective-C 2.0: мне интересно, что означает « отправка сообщения на ноль », не говоря уже о том, насколько это действительно полезно. Взяв отрывок из документации:

В Какао есть несколько шаблонов, которые используют этот факт. Значение, возвращаемое из сообщения в nil, также может быть действительным:

  • Если метод возвращает объект, любой тип указателя, любой целочисленный скаляр размера меньше или равный sizeof (void *), float, double, a long double или long long, то сообщение, отправленное в nil, возвращает 0 .
  • Если метод возвращает структуру, как определено в Руководстве по вызову функций Mac OS X ABI, которая должна быть возвращена в регистрах, то сообщение, отправленное в nil, возвращает 0,0 для каждого поля в структуре данных. Другие типы данных структуры не будут заполняться нулями.
  • Если метод возвращает что-либо, кроме вышеупомянутых типов значений, возвращаемое значение сообщения, отправленного в nil, не определено.

Неужели Java сделала мой мозг неспособным понять приведенное выше объяснение? Или мне не хватает чего-то, что сделало бы это таким же прозрачным, как стекло?

Я понимаю, что такое сообщения / получатели в 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, когда он попытается получить доступ к списку, в данном случае при вызове 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 равен nil / NULL, NSLog будет передаваться nil каждый раз в цикле. (Хотя NSLog - это функция, а не метод, он выводит (null), если передано nil NSString.

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

Майкл Бакли
источник
33
Вероятно, стоит упомянуть, что это поведение было предметом множества дискуссий в сообществе Objective-C за последние пару десятилетий. Компромисс между "безопасностью" и "удобством" по-разному оценивается разными людьми.
Марк Бесси
3
На практике существует большая симметрия между передачей сообщений в nil и тем, как работает Objective-C, особенно в новой функции слабых указателей в ARC. Слабые указатели автоматически обнуляются. Так что разработайте свой API так, чтобы он мог отвечать на 0 / nil / NIL / NULL и т. Д.
Cthutu
1
Я думаю, что если вы это сделаете, myObject->iVarон выйдет из строя, независимо от того, C с объектами или без них . (извините за могилу)
11684
3
@ 11684 Это правильно, но ->это уже не операция Objective-C, а скорее общий C-ism.
bbum 05
1
Недавний корень OSX эксплуатирует / скрытый бэкдор апи доступен для всех пользователей ( а не только админы) из - за Obj-C в Nil сообщений.
dcow
51

Сообщение для nilничего не делает и возвращает nil, Nil, NULL, 0, или 0.0.

Питер Хози
источник
41

Все остальные сообщения верны, но, возможно, здесь важна концепция.

В вызовах методов Objective-C любая ссылка на объект, которая может принимать селектор, является допустимой целью для этого селектора.

Это сэкономит МНОГО от «является ли целевой объект типа X?» code - до тех пор, пока получающий объект реализует селектор, совершенно не имеет значения, какой это класс! nilэто NSObject, который принимает любой селектор - он просто ничего не делает . Это также избавляет от большого количества кода «проверка на ноль, не отправлять сообщение, если истинно». (Концепция «если он это принимает, то реализует» - это также то, что позволяет вам создавать протоколы , которые в некотором роде похожи на интерфейсы Java: декларация о том, что если класс реализует указанные методы, то он соответствует протоколу.)

Причина в том, чтобы исключить код обезьяны, который ничего не делает, кроме как радует компилятор. Да, вы получаете накладные расходы на вызов еще одного метода, но вы экономите время программиста , которое является гораздо более дорогим ресурсом, чем время процессора. Кроме того, вы устраняете из своего приложения больше кода и большую условную сложность.

Разъяснение для тех, кто проголосовал против: вы можете подумать, что это не лучший способ, но это то, как реализован язык, и это рекомендуемая идиома программирования в Objective-C (см. Стэнфордские лекции по программированию для iPhone).

Джо МакМахон
источник
17

Это означает, что среда выполнения не выдает ошибки, когда objc_msgSend вызывается для указателя nil; вместо этого он возвращает некоторое (часто полезное) значение. Сообщения, которые могут иметь побочный эффект, ничего не делают.

Это полезно, потому что большинство значений по умолчанию более подходят, чем ошибка. Например:

[someNullNSArrayReference count] => 0

Т.е., nil оказывается пустым массивом. Скрытие нулевой ссылки NSView ничего не делает. Удобно, а?

Богатый
источник
12

В цитате из документации есть две отдельные концепции - возможно, было бы лучше, если бы в документации это было более ясно:

В Какао есть несколько шаблонов, которые используют этот факт.

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

Первое, вероятно, здесь более уместно: как правило, возможность отправлять сообщения для 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.

mmalc
источник
12

От Greg Parker «S сайте :

При запуске LLVM Compiler 3.0 (Xcode 4.2) или новее

Сообщения к нулю с возвращаемым типом | возвращение
Целые числа до 64 бит | 0
С плавающей запятой до двойного числа | 0,0
Указатели | ноль
Структуры | {0}
Любой _Сложный тип | {0, 0}
Heath Borders
источник
9

Это означает, что часто не нужно везде проверять отсутствие объектов на предмет безопасности, в частности:

[someVariable release];

или, как уже отмечалось, все методы count и length возвращают 0, когда у вас есть значение nil, поэтому вам не нужно добавлять дополнительные проверки на nil повсюду:

if ( [myString length] > 0 )

или это:

return [myArray count]; // say for number of rows in a table
Кендалл Хельмштеттер Гельнер
источник
Имейте в виду, что другая сторона медали - это потенциальная возможность появления ошибок, таких как «if ([myString length] == 1)»
hatfinch
Как это ошибка? [myString length] возвращает ноль (nil), если myString равна nil ... одна вещь, которая, я думаю, может быть проблемой, - это [myView frame], который, я думаю, может дать вам что-то странное, если myView равен нулю.
Кендалл Хельмштеттер Гельнер 01
Если вы разрабатываете свои классы и методы вокруг концепции, что значения по умолчанию (0, nil, NO) означают «бесполезно», это мощный инструмент. Мне никогда не нужно проверять свои строки на ноль перед проверкой длины. Для меня пустая строка бесполезна и пустая строка, когда я обрабатываю текст. Я также разработчик Java, и я знаю, что сторонники Java будут избегать этого, но это экономит много кода.
Джейсон Фюрстенберг
6

Не думайте о том, что «получатель ноль»; Я согласен, что это довольно странно. Если вы отправляете сообщение на nil, получателя нет. Вы просто никуда не отправляете сообщение.

Как с этим справиться - это философское различие между Java и Objective-C: в Java это ошибка; в Objective-C это не работает.

Benzado
источник
В java есть исключение из этого поведения: если вы вызываете статическую функцию с нулевым значением, это эквивалентно вызову функции в классе времени компиляции переменной (не имеет значения, является ли она нулевой).
Роман А. Тайчер
6

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

Никита Жук
источник
6

Я не думаю, что в других ответах это четко упоминалось: если вы привыкли к Java, вы должны иметь в виду, что, хотя Objective-C в Mac OS X имеет поддержку обработки исключений, это дополнительная языковая функция, которая может быть включается / выключается флагом компилятора. Я предполагаю, что этот дизайн «отправка сообщений в nilбезопасное место» предшествует включению поддержки обработки исключений в язык и был сделан с той же целью: методы могут возвращаться, nilчтобы указать на ошибки, и поскольку отправка сообщения nilобычно возвращаетnilв свою очередь, это позволяет индикации ошибки распространяться по вашему коду, поэтому вам не нужно проверять ее при каждом сообщении. Вам нужно только проверить это в тех точках, где это важно. Я лично считаю, что распространение и обработка исключений - лучший способ достичь этой цели, но не все могут с этим согласиться. (С другой стороны, мне, например, не нравится требование Java о том, что вы должны объявлять, какие исключения может генерировать метод, что часто вынуждает вас синтаксически распространять объявления исключений по всему вашему коду; но это другое обсуждение.)

Я опубликовал аналогичный, но более длинный ответ на связанный с этим вопрос «Требуется ли утверждение, что создание каждого объекта выполнено успешно в Objective C?» если вам нужны подробности.

Ринзвинд
источник
Я никогда не думал об этом так. Это действительно кажется очень удобной функцией.
mk12 02
2
Хорошая догадка, но исторически неточная в отношении того, почему было принято решение. Обработка исключений существовала в языке с самого начала, хотя исходные обработчики исключений были довольно примитивными по сравнению с современной идиомой. Nil-eats-message был сознательным выбором дизайна, основанным на необязательном поведении объекта Nil в Smalltalk. Когда были разработаны оригинальные API-интерфейсы NeXTSTEP, цепочка методов была довольно распространенной, а nilвозврат часто использовался для короткого замыкания цепочки в NO-op.
bbum
2

C ничего не представляет как 0 для примитивных значений и NULL для указателей (что эквивалентно 0 в контексте указателя).

Objective-C строится на представлении C ничего, добавляя nil. nil - это объектный указатель ни на что. Хотя семантически отличны от NULL, они технически эквивалентны друг другу.

Недавно выделенные объекты NSObject начинают свою жизнь с их содержимым, установленным на 0. Это означает, что все указатели, которые объект имеет на другие объекты, начинаются с nil, поэтому нет необходимости, например, устанавливать self. (Association) = nil в методах инициализации.

Однако наиболее примечательным поведением nil является то, что ему могут отправляться сообщения.

В других языках, таких как 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, позволяет этому удобству быть функцией, а не скрытой ошибкой в ​​вашем приложении. Обязательно защитите себя от случаев, когда значения nil нежелательны, либо проверяя и возвращая на раннем этапе для молчаливого сбоя, либо добавляя NSParameterAssert для создания исключения.

Источник: http://nshipster.com/nil/ https://developer.apple.com/library/ios/#documentation/cocoa/conceptual/objectivec/Chapters/ocObjectsClasses.html (отправка сообщения на ноль).

Зи
источник