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

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

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

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

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

Я получаю представление о сообщениях / получателях в Objective-C, я просто запутался в том, что получатель nil,

11 ответов

Решение

Ну, я думаю, что это можно описать с помощью очень надуманного примера. Допустим, у вас есть метод в 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 будет получен доступ в [an Array count]. Однако, вместо того, чтобы выдавать исключение NullPointerException, Objective-C просто вернет 0 в соответствии с приведенными выше правилами, поэтому цикл не будет выполняться. Однако, если мы установим цикл на выполнение заданного количества раз, то сначала мы отправим сообщение an Array в [anArray objectAtIndex:i]; Это также вернет 0, но поскольку objectAtIndex: возвращает указатель, а указатель на 0 равен nil/NULL, NSLog будет пропущен nil каждый раз через цикл. (Хотя NSLog является функцией, а не методом, он выводит (ноль), если передан ноль NSString.

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

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

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

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

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

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

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

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

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

[someNullNSArrayReference count] => 0

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

С сайта Грега Паркера:

Если работает LLVM Compiler 3.0 (Xcode 4.2) или новее

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

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

Есть несколько моделей в Какао, которые используют в своих интересах этот факт.

Значение, возвращаемое из сообщения в 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,

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

[someVariable release];

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

if ( [myString length] > 0 )

или это:

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

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

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

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

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

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

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

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

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

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

В других языках, таких как C++ (или Java), это может привести к сбою вашей программы, но в Objective-C вызов метода для nil возвращает нулевое значение. Это значительно упрощает выражения, так как избавляет от необходимости проверять 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 (отправка сообщения на ноль).

Другие вопросы по тегам