在Objective-C中向nil发送消息


107

作为正在阅读Apple的Objective-C 2.0文档的Java开发人员:我想知道“ 将消息发送到nil ”的含义是什么-更不用说它的实际用途了。从文档摘录:

可可中有几种模式可以利用这一事实。从消息返回nil的值也可能有效:

  • 如果该方法返回一个对象,任何指针类型,任何小于或等于sizeof(void *)的整数标量,浮点数,double,long double或long long,则发送给nil的消息将返回0 。
  • 如果该方法返回如Mac OS X ABI函数调用指南所定义的要在寄存器中返回的结构,则发送到nil的消息将为数据结构中的每个字段返回0.0。其他结构数据类型将不会用零填充。
  • 如果该方法返回上述值类型以外的任何值,则发送到nil的消息的返回值不确定。

Java是否使我的大脑无法理解上面的解释?还是我想念的东西像玻璃一样清晰?

我确实在Objective-C中有了消息/接收者的主意,我对恰好是的接收者感到困惑nil


2
我也有Java背景,一开始就被这个漂亮的功能吓坏了,但是现在我发现它绝对可爱!
Valentin Radu

1
谢谢,这是一个很好的问题。您是否看过以了解其好处?这让我印象深刻,这是“不是错误,而是功能”。我不断发现Java会给我一个例外的错误,所以我知道问题出在哪里。我不乐意使用null指针异常来在这里和那里保存一两行琐碎的代码。
MaciejTrybiło2012年

Answers:


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()的调用。现在,您可能永远都不会这样调用NULL值的someObject.foo(NULL)。但是,您可能已经从返回NULL的方法中获取了ArrayList,如果该方法遇到生成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。将首先在[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。


33
值得一提的是,在过去的几十年中,这种行为一直是Objective-C社区中众多争论的主题。不同的人对“安全”和“便利”之间的权衡进行了不同的评估。
Mark Bessey,

3
在实践中,将消息传递到nil与Objective-C的工作方式之间存在很多对称性,尤其是在ARC中新的弱指针功能中。弱指针将自动清零。所以,设计你的API,以便它可以响应0 /无/ NIL / NULL等
Cthutu

1
我认为,如果这样做myObject->iVar,它将崩溃,无论它是带有带有对象的C。(抱歉gravedig。)
11684

3
@ 11684是正确的,但->不再是Objective-C操作,而是非常通用的C-ism。
bbum 2013年

1
最近的OSX根利用/隐藏后门API是因为OBJ-C的无消息的所有用户(不只是管理员)访问。
dcow 2015年

51

一则消息,nil不执行任何操作,并返回nilNilNULL0,或0.0


41

所有其他帖子都是正确的,但也许在这里重要的是概念。

在Objective-C方法调用中,任何可以接受选择器的对象引用都是该选择器的有效目标。

这样可以保存很多“目标对象是否为X类型?” 代码-只要接收对象实现选择器,它是什么类就绝对没有区别nil是一个接受任何选择器的NSObject-它什么都不。这也消除了很多“检查是否为零,如果为true则不发送消息”的代码。(“如果接受,就实现”概念也使您可以创建协议,有点像Java接口:一个声明,如果一个类实现了所声明的方法,则它符合该协议。)

这样做的原因是消除了猴子代码,除了让编译器满意之外,它什么也没做。是的,您可以再负担一次方法调用的开销,但可以节省程序员的时间,这比CPU时间要昂贵得多。另外,您正在从应用程序中消除更多的代码和更多的条件复杂性。

为低俗人士澄清:您可能会认为这不是一个好方法,但是这是实现语言的方式,并且是Objective-C中推荐的编程习惯用法(请参阅Stanford iPhone编程讲座)。


17

这意味着在nil指针上调用objc_msgSend时,运行时不会产生错误。而是返回一些(通常是有用的)值。可能有副作用的邮件无济于事。

这很有用,因为大多数默认值比错误更合适。例如:

[someNullNSArrayReference count] => 0

即,nil似乎是空数组。隐藏nil NSView参考没有任何作用。方便吗?


12

在文档中引用的内容中有两个单独的概念-如果文档更清楚一些,也许会更好:

可可中有几种模式可以利用这一事实。

从消息返回nil的值也可能有效:

前者在这里可能更相关:通常能够发送消息以nil使代码更简单-您不必在所有地方检查空值。规范的示例可能是访问器方法:

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

如果向其发送消息nil无效,则此方法将更加复杂-您必须另外进行两次检查以确保发送消息valuenewValue而不是nil在发送消息之前。

不过,后一点(从消息返回的值nil通常也是有效的)为前者增加了乘数效应。例如:

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

这段代码再次不需要检查nil值,并且自然流...

综上所述,能够发送消息的额外灵活性nil确实要付出一定的代价。您可能会在某个阶段以一种特殊的方式编写失败的代码,因为您没有考虑到值可能为的可能性nil


12

格雷格·帕克Greg Parker)的网站

如果运行LLVM Compiler 3.0(Xcode 4.2)或更高版本

返回类型为nil的消息| 返回
整数,最大64位| 0
浮点数加长 0.0
指针 零
结构| {0}
任何_Complex类型| {0,0}

9

这意味着通常不必为了安全起见在所有地方都检查零对象-特别是:

[someVariable release];

或者,如前所述,当您获得nil值时,各种count和length方法都将返回0,因此您不必为nil额外添加额外的检查:

if ( [myString length] > 0 )

或这个:

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

请记住,硬币的另一面是潜在的错误,例如“ if([[myString length] == 1)”
hatfinch 2010年

那是什么bug?如果myString为零,则[myString length]会返回零(nil)。我认为可能是一个问题是[myView frame],如果myView为nil,这可能会给您带来古怪的感觉。
肯德尔·赫尔姆斯特·盖尔纳

如果围绕默认值(0,nil,NO)表示“无用”的概念设计类和方法,则这是一个功能强大的工具。在检查长度之前,我不必检查字符串是否为零。对我来说,在我处理文本时,空字符串是无用的,而nil字符串是无用的。我也是Java开发人员,我知道Java纯粹主义者会避免这样做,但是这样可以节省很多代码。
杰森·弗森伯格

6

不要认为“接收者为零”;我同意,这很奇怪的。如果您要发送的消息为nil,则没有接收者。您只是在发送一条消息而已。

如何处理这是Java与Objective-C之间的哲学差异:在Java中,这是一个错误;在Objective-C中,它是无操作的。


在Java中,该行为有一个例外,如果您在null上调用静态函数,则等同于在变量的编译时类上调用该函数(是否为null无关紧要)。
罗曼·泰切

6

发送到nil且其返回值大于sizeof(void *)的ObjC消息在PowerPC处理器上产生未定义的值。除此之外,这些消息还导致未定义的值也返回到大小大于8字节(在Intel处理器上)的结构的字段中。文森特·盖布尔(Vincent Gable)在他的博客文章中对此进行了很好的描述


6

我认为没有其他答案能清楚地提及这一点:如果您习惯使用Java,则应记住,尽管Mac OS X上的Objective-C具有异常处理支持,但它是一种可选的语言功能,可以使用编译器标志打开/关闭。我的猜测是,这种“向nil安全发送消息”的设计要早于在语言中包含异常处理支持,并且这样做的初衷是类似的:方法可以返回nil以指示错误,并且自从将消息发送至nil通常返回nil反过来,这允许错误指示在您的代码中传播,因此您不必在每条消息中都进行检查。您只需要在重要的地方进行检查即可。我个人认为异常传播和处理是解决此目标的更好方法,但并非所有人都对此表示赞同。(另一方面,例如,我不喜欢Java的要求,即您必须声明方法可能抛出的异常,这通常迫使您在整个代码上句法传播异常声明;但这是另一个讨论。)

我对相关问题“是否断言在Objective C中成功创建每个对象都是成功的?”的答案类似但更长如果您需要更多详细信息。


我从来没有那样想过。这似乎是一个非常方便的功能。
mk12'2

2
很好的猜测,但是历史上对于做出该决定的原因并不准确。从一开始就在语言中存在异常处理,尽管与现代习语相比,原始的异常处理程序非常原始。Nil-eats-message是自Smalltalk中Nil对象的可选行为派生的有意识的设计选择。当设计原始的NeXTSTEP API时,方法链接是相当普遍的,并且nil经常使用返回键将链短路成NO-OP。
bbum 2013年

2

对于原始值,C表示为0,对于指针,表示NULL(在指针上下文中等于0)。

通过添加nil,Objective-C以C的无表示为基础。nil是什么都没有的对象指针。尽管从语义上与NULL不同,但它们在技术上彼此等效。

新分配的NSObjects从其内容设置为0开始生存。这意味着该对象指向其他对象的所有指针均以nil开始,因此,例如,无需在init方法中设置self。(association)= nil。

但是,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"]) { ... }

知道Objective-C中nil是如何工作的,这使此便利成为一项功能,而不是应用程序中的潜在错误。确保通过检查并尽早返回以静默失败,或者添加NSParameterAssert引发异常来防止不必要的nil值。

来源:http : //nshipster.com/nil/ https://developer.apple.com/library/ios/#documentation/cocoa/conceptual/objectivec/Chapters/ocObjectsClasses.html(将消息发送到nil)。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.