在Objective-C中向nil发送消息
作为正在阅读Apple的Objective-C 2.0文档的Java开发人员:我想知道"将消息发送为nil"意味着什么,更不用说它实际上有什么用了。摘录自文档:
There are several patterns in Cocoa that take advantage of this fact. The value returned from a message to nil may also be valid: If the method returns an object, any pointer type, any integer scalar of size less than or equal to sizeof(void*), a float, a double, a long double, or a long long, then a message sent to nil returns 0. If the method returns a struct, as defined by the Mac OS X ABI Function Call Guide to be returned in registers, then a message sent to nil returns 0.0 for every field in the data structure. Other struct data types will not be filled with zeros. If the method returns anything other than the aforementioned value types the return value of a message sent to nil is undefined.
Java是否使我的大脑无法理解上面的解释?还是我想念的东西像玻璃一样清晰?
我确实在Objective-C中有了消息/接收者的想法,我只是对碰巧是" nil"的接收者感到困惑。
解决方案
这意味着在nil指针上调用objc_msgSend时,运行时不会产生错误。而是返回一些(通常是有用的)值。可能有副作用的邮件不会执行任何操作。
这很有用,因为大多数默认值比错误更合适。例如:
[someNullNSArrayReference count] => 0
即,nil似乎是空数组。隐藏nil NSView参考没有任何作用。方便吗?
好吧,我认为可以用一个非常人为的例子来描述它。假设我们在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()的调用。现在,我们可能永远都不会这样调用NULL值的someObject.foo(NULL)。但是,我们可能已经从返回NULL的方法中获取了ArrayList,如果该方法遇到生成ArrayList的错误,例如someObject.foo(otherObject.getArrayList());则返回NULL。
当然,如果我们执行以下操作,也会遇到问题:
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。将首先在[anArray count]处访问nil对象。但是,按照上面的规则,Objective-C不会返回NullPointerException,而是直接返回0,因此不会运行该循环。但是,如果我们将循环设置为运行一定次数,那么我们首先要通过[anArray objectAtIndex:i]向anArray发送一条消息;这也将返回0,但是由于objectAtIndex:返回一个指针,并且指向0的指针为nil / NULL,因此每次通过循环时,NSLog都会被传递为nil。 (尽管NSLog是一个函数而不是一个方法,但是如果传递了nil NSString,它将输出(空)。
在某些情况下,拥有NullPointerException更好,因为我们可以立即告诉程序有问题,但是除非捕获到异常,否则程序将崩溃。 (在C语言中,尝试以这种方式取消对NULL的引用会导致程序崩溃。)在Objective-C中,它只会导致可能不正确的运行时行为。但是,如果我们有一个方法在返回0 / nil / NULL /零结构时不会中断,那么就不必检查对象或者参数是否为nil。
这意味着通常不必为了安全而到处检查零个对象,尤其是:
[someVariable release];
或者,如上所述,当我们获得nil值时,各种count和length方法都将返回0,因此我们不必为nil额外添加额外的检查:
if ( [myString length] > 0 )
或者这个:
return [myArray count]; // say for number of rows in a table
不要认为"接收者为零";我同意,这很奇怪。如果我们要发送的消息为nil,则没有接收者。我们只是在发送一条消息而已。
如何处理这是Java与Objective-C之间的哲学差异:在Java中,这是一个错误;在Java中,这是一个错误。在Objective-C中,它是无操作的。
在文档的引文中,有两个单独的概念-也许文档更清楚一点可能会更好:
There are several patterns in Cocoa that take advantage of this fact. The value returned from a message to nil may also be valid:
前者在这里可能更相关:通常能够将消息发送到`nil'使代码更直接-我们不必在所有地方检查null值。规范的示例可能是访问器方法:
- (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'的可能性。
一条向nil
的消息不执行任何操作,并返回nil
,Nil
,NULL
,0
或者0.0
。
所有其他帖子都是正确的,但也许在这里重要的是概念。
在Objective-C方法调用中,任何可以接受选择器的对象引用都是该选择器的有效目标。
这样可以保存很多"目标对象是否为X类型?"只要接收对象实现了选择器,代码绝对不会改变它的类! nil是一个NSObject,它接受任何选择器,只是不执行任何操作。这也消除了很多"检查是否为零,如果为true则不发送消息"的代码。 ("如果接受,就实现"的概念也使我们可以创建协议,有点像Java接口:一个声明,如果一个类实现了所声明的方法,那么它就符合该协议。)
这样做的原因是消除了猴子代码,除了让编译器感到满意之外,它什么都不做。是的,我们再获得一个方法调用的开销,但是却节省了程序员的时间,这是比CPU时间昂贵得多的资源。另外,我们正在从应用程序中消除更多的代码和更多的条件复杂性。
为低俗人士澄清:我们可能会认为这不是一个好方法,但这是实现语言的方式,并且是Objective-C中推荐的编程习惯用法(请参阅Stanford iPhone编程讲座)。
发送到nil且其返回值的大小大于sizeof(void *)的ObjC消息在PowerPC处理器上产生未定义的值。除此之外,这些消息还导致未定义的值也将在大小大于8字节的结构(在Intel处理器上)中返回。文森特·盖布尔(Vincent Gable)在他的博客文章中对此做了很好的描述