отправка сообщения на ноль в objective-c

<p>как java разработчик, который читает документацию apple objective-c 2.0: мне интересно, что означает "<em>отправка сообщения nil</em>" - не говоря уже о том, как это на самом деле полезно. взяв отрывок из документации:</p><blockquote> <p>есть несколько моделей в какао, которые используют в своих интересах этот факт. значение, возвращаемое из сообщения в nil, также может быть допустимым:</p> <ul> <li>если метод возвращает объект, любой тип указателя, любой целочисленный скаляр с размером, меньшим или равным sizeof (void *), с плавающей запятой, double, long double или long long, то сообщение, отправленное на nil, возвращает 0 ,.</li> <li>если метод возвращает структуру, как определено в руководстве по вызову функции abi для mac os x, для возврата в регистры, то сообщение, отправленное на ноль, возвращает 0.0 для каждого поля в структуре данных. другие типы данных структуры не будут заполнены нулями.</li> <li>если метод возвращает что-либо кроме вышеупомянутых типов значений, возвращаемое значение сообщения, отправленного на ноль, не определено.</li> </ul> </blockquote><p>java сделал мой мозг неспособным ухитриться из объяснения выше? или что-то, чего мне не хватает, сделало бы это ясным, как стекло?</p><p>я получил представление о сообщениях/получателях в objective-c, я просто запутался в том, что получатель <code>nil</code>.</p>

<p>ну, я думаю, что это можно описать, используя очень надуманный пример. допустим, у вас есть метод в java, который распечатывает все элементы в arraylist:</p><pre><code>void foo(arraylist list) { for(int i = 0; i < list.size(); ++i){ system.out.println(list.get(i).tostring()); } } </code></pre><p>теперь, если вы вызываете этот метод следующим образом: someobject.foo (null); вы, вероятно, получите исключение nullpointerexception при попытке доступа к списку, в данном случае при вызове list.size (); теперь вы, вероятно, никогда бы не вызвали someobject.foo (null) с таким значением null. однако вы, возможно, получили свой arraylist от метода, который возвращает null, если он сталкивается с некоторой ошибкой, генерирующей arraylist, например someobject.foo (otherobject.getarraylist ());</p><p>конечно, у вас также будут проблемы, если вы сделаете что-то вроде этого:</p><pre><code>arraylist list = null; list.size(); </code></pre><p>теперь в objective-c у нас есть эквивалентный метод:</p><pre><code>- (void)foo:(nsarray*)anarray { int i; for(i = 0; i < [anarray count]; ++i){ nslog(@"%@", [[anarray objectatindex:i] stringvalue]; } } </code></pre><p>теперь, если у нас есть следующий код:</p><pre><code>[someobject foo:nil]; </code></pre><p>у нас такая же ситуация, когда java создает исключение nullpointerexception. сначала к объекту nil будет получен доступ в [anarray count]. однако, вместо исключения nullpointerexception, objective-c просто вернет 0 в соответствии с приведенными выше правилами, поэтому цикл не будет выполняться. однако, если мы установим цикл на выполнение заданного числа раз, то сначала мы отправим сообщение в anarray в [anarray objectatindex: i]; это также вернет 0, но поскольку objectatindex: возвращает указатель, а указатель на 0 равен nil/null, nslog будет пропущен nil каждый раз через цикл. (хотя nslog является функцией, а не методом, он выводит (ноль), если передан ноль nsstring.</p><p>в некоторых случаях лучше иметь исключение nullpointerexception, поскольку вы можете сразу сказать, что с программой что-то не так, но если вы не уловите исключение, программа завершится сбоем. (в c попытка разыменования null таким способом приводит к сбою программы.) в objective-c вместо этого это просто вызывает, возможно, некорректное поведение во время выполнения. однако, если у вас есть метод, который не прерывается, если он возвращает 0/nil/null/нулевую структуру, то это избавляет вас от необходимости проверять, чтобы убедиться, что объект или параметры равны нулю.</p>

<p>сообщение <code>nil</code> ничего не делает и возвращает <code>nil</code>, <code>nil</code>, <code>null</code>, <code>0</code> или <code>0.0</code>.</p>

<p>все остальные сообщения верны, но, возможно, здесь важна концепция.</p><p>в вызовах метода objective c любая ссылка на объект, которая может принять селектор, является допустимой целью для этого селектора.</p><p>это сохраняет много "является целевым объектом типа x?" код - пока принимающий объект реализует селектор, он делает <em>абсолютно без разницы</em> какой это класс! <code>nil</code> - это объект nso, который принимает любой селектор - он просто не <em>делает</em> ничего. это исключает большую часть кода "проверьте на ноль, не отправляйте сообщение, если оно истинно". (концепция "если он принимает это, он реализует ее" также позволяет вам создавать <em>протоколы</em>, что-то вроде интерфейсов java: объявление, которое, если класс реализует заявленные методы, то это соответствует протоколу.)</p><p>причина этого заключается в том, чтобы исключить обезьяний код, который ничего не делает, кроме того, что компилятор доволен да, вы получаете накладные расходы еще на один вызов метода, но вы экономите <em>время программиста</em>, что намного дороже, чем процессорное время. кроме того, вы исключаете из своего приложения больше кода и усложняете условия.</p><p>разъяснение для downvoters: вы можете подумать, что это не очень хороший путь, но именно так реализован язык, и это рекомендуемая идиома программирования <em>в objective-c</em> (см. лекции по программированию в stanford для iphone).</p>

<p>это означает, что среда выполнения не выдает ошибку, когда objc\_msgsend вызывается по нулевому указателю; вместо этого он возвращает некоторое (часто полезное) значение. сообщения, которые могут иметь побочный эффект, ничего не делают.</p><p>это полезно, потому что большинство значений по умолчанию являются более подходящими, чем ошибка. например:</p><pre><code>[somenullnsarrayreference count] => 0 </code></pre><p>т.е. nil представляется пустым массивом. скрытие нулевой ссылки nsview ничего не делает. удобно, а?</p>

<p>в цитате из документации есть две отдельные концепции - возможно, было бы лучше, если бы в документации это было более понятно:</p><blockquote> <p>есть несколько моделей в какао, которые используют в своих интересах этот факт.</p> <p>значение, возвращаемое из сообщения в nil, также может быть допустимым:</p> </blockquote><p>первое здесь, вероятно, более уместно: обычно возможность отправлять сообщения <code>nil</code> делает код более простым - вам не нужно везде проверять наличие нулевых значений. каноническим примером, вероятно, является метод доступа:</p><pre><code>- (void)setvalue:(myclass *)newvalue { if (value != newvalue) { [value release]; value = [newvalue retain]; } } </code></pre><p>если отправка сообщений в <code>nil</code> была недопустимой, этот метод был бы более сложным - вам нужно было бы провести две дополнительные проверки, чтобы убедиться, что <code>value</code> и <code>newvalue</code> не являются <code>nil</code> перед отправкой им сообщений.</p><p>последний пункт (что значения, возвращаемые из сообщения <code>nil</code>, также обычно действительны), тем не менее, добавляет эффект умножения к первому. например:</p><pre><code>if ([myarray count] > 0) { // do something... } </code></pre><p>этот код снова не требует проверки значений <code>nil</code> и работает естественно ...</p><p>все это говорит о том, что дополнительная гибкость, позволяющая отправлять сообщения <code>nil</code>, имеет определенную стоимость. существует вероятность того, что на каком-то этапе вы будете писать код, который приводит к сбою особым образом, потому что вы не учли возможность того, что значением может быть <code>nil</code>.</p>

<p>от <a href="https://twitter.com/gparker">грег паркер</a> --- <a href="http://www.sealiesoftware.com/blog/archive/2012/2/29/objc\_explain\_return\_value\_of\_message\_to\_nil.html">сайт</a> :</p><p>если работает llvm compiler 3.0 (xcode 4.2) или новее</p><pre> сообщения на ноль с типом возврата | вернуть целые числа до 64 бит | 0 с плавающей точкой до длинных двойных | 0.0 указатели | ноль структуры | {0} любой \_сложный тип | {0, 0} </pre>

<p>это часто означает отсутствие необходимости проверять нигде объекты на предмет безопасности, особенно:</p><pre><code>[somevariable release]; </code></pre><p>или, как уже отмечалось, различные методы count и length возвращают 0, когда вы получаете значение nil, поэтому вам не нужно добавлять дополнительные проверки для nil во всем:</p><pre><code>if ( [mystring length] > 0 ) </code></pre><p>или это:</p><pre><code>return [myarray count]; // say for number of rows in a table </code></pre>

<p>сообщения objc, которые отправляются на ноль и возвращаемые значения имеют размер больше, чем sizeof (void *), создают неопределенные значения на процессорах powerpc. кроме того, эти сообщения приводят к тому, что неопределенные значения возвращаются в полях структур, размер которых также превышает 8 байтов на процессорах intel. винсент гейбл хорошо описал это в своем <a href="http://vgable.com/blog/2008/05/31/messages-to-nowhere/">сообщение в блоге</a> </p>

<p>не думайте о том, что "получатель равен нулю"; я согласен, что <em>это</em> довольно странно. если вы отправляете сообщение на ноль, получателя нет. вы просто отправляете сообщение ни к чему.</p><p>как справиться с этим - философское различие между java и objective-c: в java это ошибка; в objective-c это не работает.</p>

<p>я не думаю, что в каких-либо других ответах это четко упоминалось: если вы привыкли к java, вы должны иметь в виду, что хотя objective-c в mac os x имеет поддержку обработки исключений, это дополнительная языковая функция, которая может быть включается/выключается с флагом компилятора. я предполагаю, что такой дизайн "отправки сообщений <code>nil</code> безопасен" предшествовал включению поддержки обработки исключений в языке и был сделан с аналогичной целью: методы могут возвращать <code>nil</code>, чтобы указать на ошибки, и после отправки сообщения <code>nil</code> обычно возвращает <code>nil</code> по очереди, это позволяет индикации ошибки распространяться по вашему коду, поэтому вам не нужно проверять ее в каждом отдельном сообщении. вы должны проверить это только в тех местах, где это имеет значение. я лично считаю, что распространение и обработка исключений - лучший способ решения этой задачи, но не все могут с этим согласиться. (с другой стороны, мне, например, не нравится требование java о том, что вы должны объявлять, какие исключения может вызывать метод, что часто вынуждает вас <em> синтаксически </em> распространять объявления исключений по всей вашей код, но это другое обсуждение.)</p><p>я опубликовал аналогичный, но более длинный ответ на связанный с этим вопрос <a href="https://stackoverflow.com/questions/5450775/is-asserting-that-every-object-creation-succeeded-necessary-in-objective-c/5451243#5451243">"требуется ли в objective c создание каждого объекта обязательно?"</a> , если вы хотите больше подробностей.</p>

<p>c ничего не представляет как 0 для примитивных значений и null для указателей (что эквивалентно 0 в контексте указателя).</p><p>objective-c основан на представлении c ничего, добавляя ноль. nil - это указатель на объект. хотя они семантически отличаются от null, они технически эквивалентны друг другу.</p><p>вновь выделенные nso-объекты начинают жизнь с их содержимым, установленным на 0. это означает, что все указатели, которые объект имеет на другие объекты, начинаются как nil, поэтому нет необходимости, например, устанавливать self. (association) = nil в методах init.</p><p><strong> однако наиболее заметным поведением nil является то, что ему могут отправляться сообщения. </strong></p><p>в других языках, таких как c++ (или java), это может привести к сбою вашей программы, но в objective-c вызов метода для nil возвращает нулевое значение. это значительно упрощает выражения, так как избавляет от необходимости проверять nil перед тем, как что-либо делать:</p><pre><code>// for example, this expression... if (name != nil && [name isequaltostring:@"steve"]) { ... } // ...can be simplified to: if ([name isequaltostring:@"steve"]) { ... } </code></pre><p>знание того, как работает nil в objective-c, позволяет использовать это удобство как функцию, а не скрытую ошибку в вашем приложении. обязательно защитите от случаев, когда значения nil являются нежелательными, либо проверяя и возвращая рано, чтобы завершиться с ошибкой, либо добавляя nsparameterassert, чтобы вызвать исключение.</p><p>источник: <a href="http://nshipster.com/nil/">http://nshipster.com/nil/</a> <a href="https://developer.apple.com/library/ios/#documentation/cocoa/conceptual/objectivec/chapters/ocobjectsclasses.html">https://developer.apple.com/library/ios/#documentation/cocoa/conceptual/objectivec/chapters/ocobjectsclasses. html</a> (отправка сообщения на ноль).</p>