107

作为正在阅读 Apple 的 Objective-C 2.0 文档的 Java 开发人员:我想知道“向 nil 发送消息”是什么意思——更不用说它实际上有什么用处了。从文档中摘录:

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

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

Java 是否让我的大脑无法理解上面的解释?还是有什么我遗漏的东西可以使它像玻璃一样清晰?

我确实了解 Objective-C 中的消息/接收器,我只是对碰巧是nil.

4

11 回答 11

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。nil 对象将首先在 [anArray count] 处被访问。但是,Objective-C 不会抛出 NullPointerException,而是按照上面的规则简单地返回 0,因此循环不会运行。但是,如果我们将循环设置为运行一定次数,那么我们首先在 [anArray objectAtIndex:i] 处向 anArray 发送消息;这也将返回 0,但是由于 objectAtIndex: 返回一个指针,并且指向 0 的指针是 nil/NULL,因此 NSLog 每次循环都会传递 nil。(虽然 NSLog 是一个函数而不是一个方法,但如果传递一个 nil NSString,它会打印出 (null)。

在某些情况下,最好有一个 NullPointerException,因为您可以立即判断程序有问题,但除非您捕获该异常,否则程序将崩溃。(在 C 中,尝试以这种方式取消引用 NULL 会导致程序崩溃。)在 Objective-C 中,它只会导致可能不正确的运行时行为。但是,如果您有一个在返回 0/nil/NULL/零结构时不会中断的方法,那么这将使您不必检查以确保对象或参数为零。

于 2008-10-01T06:32:29.183 回答
51

消息 tonil什么都不做并返回nil, Nil, NULL, 0, 或0.0.

于 2008-11-10T02:44:22.340 回答
41

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

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

这节省了很多“目标对象是 X 类型吗?” 代码 - 只要接收对象实现选择器,它是什么类绝对没有区别!nil是一个接受任何选择器的 NSObject - 它只是不做任何事情。这也消除了许多“检查零,如果为真则不发送消息”代码。(“如果它接受它,它就实现它”的概念也允许您创建协议,这有点像 Java 接口:一个声明,如果一个类实现了规定的方法,那么它就符合协议。)

这样做的原因是为了消除除了让编译器满意之外什么都不做的猴子代码。是的,您获得了多一次方法调用的开销,但您节省了程序员的时间,这是比 CPU 时间昂贵得多的资源。此外,您正在从应用程序中消除更多代码和更多条件复杂性。

向反对者澄清:您可能认为这不是一个好方法,但这是语言的实现方式,它是Objective-C中推荐的编程习惯(参见斯坦福 iPhone 编程讲座)。

于 2008-11-21T21:15:08.703 回答
18

这意味着当在 nil 指针上调用 objc_msgSend 时,运行时不会产生错误;相反,它返回一些(通常有用的)值。可能有副作用的消息什么都不做。

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

[someNullNSArrayReference count] => 0

即, nil 似乎是空数组。隐藏一个 nil NSView 引用什么都不做。好用,嗯?

于 2008-10-01T06:12:11.353 回答
12

在文档的引文中,有两个独立的概念——如果文档更清楚地说明这一点可能会更好:

Cocoa 中有几种模式利用了这一事实。

从消息返回到 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.

于 2008-10-12T20:02:55.137 回答
12

格雷格帕克网站

如果运行 LLVM 编译器 3.0 (Xcode 4.2) 或更高版本

返回类型为 nil 的消息 | 返回
高达 64 位的整数 | 0
浮点数高达 long double | 0.0
指针 | 零
结构 | {0}
任何_复杂类型 | {0, 0}
于 2012-02-29T22:42:18.087 回答
9

这意味着通常不必为了安全而在各处检查 nil 对象 - 特别是:

[someVariable release];

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

if ( [myString length] > 0 )

或这个:

return [myArray count]; // say for number of rows in a table
于 2008-10-01T06:53:01.460 回答
6

不要想着“接收者为零”;我同意,这奇怪。如果您向 nil 发送消息,则没有接收者。你只是向任何人发送消息。

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

于 2008-10-02T00:56:57.483 回答
6

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

于 2009-03-14T21:41:19.617 回答
6

我认为其他任何答案都没有明确提到这一点:如果您习惯于 Java,您应该记住,虽然 Mac OS X 上的 Objective-C 具有异常处理支持,但它是一个可选的语言功能,可以使用编译器标志打开/关闭。我的猜测是,这种“发送消息nil是安全的”的设计早于在语言中包含异常处理支持,并且考虑到类似的目标:方法可以返回nil以指示错误,并且由于发送消息nil通常返回nil反过来,这允许错误指示通过您的代码传播,因此您不必在每条消息中检查它。您只需要在重要的地方检查它。我个人认为异常传播和处理是实现这一目标的更好方法,但并不是每个人都同意这一点。(另一方面,例如,我不喜欢 Java 要求您必须声明一个方法可能抛出的异常,这通常会迫使您在整个代码中在语法上传播异常声明;但这是另一个讨论。)

我已经发布了一个类似但更长的回答相关问题“在Objective C中断言每个对象创建都成功了吗?” 如果您想了解更多详细信息。

于 2011-04-03T15:41:10.557 回答
2

对于原始值,C 不表示 0,而对于指针则表示 NULL(在指针上下文中相当于 0)。

Objective-C 通过添加 nil 来构建 C 的无表示。nil 是一个无指向的对象指针。尽管在语义上与 NULL 不同,但它们在技术上是等价的。

新分配的 NSObject 开始生命时其内容设置为 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"]) { ... }

了解 nil 在 Objective-C 中是如何工作的,可以让这种便利成为一个特性,而不是应用程序中潜伏的错误。确保防止出现不需要的 nil 值的情况,方法是检查并提前返回以静默失败,或者添加 NSParameterAssert 以引发异常。

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

于 2014-11-24T12:52:47.080 回答