在Objective-C中将消息发送到nil

Sending a message to nil in Objective-C

作为一个正在阅读Apple的Objto-C 2文档的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的接收器感到困惑。


嗯,我认为可以用一个非常做作的例子来描述它。假设爪哇中有一个方法打印列阵列表中的所有元素:

1
2
3
4
5
6
void foo(ArrayList list)
{
    for(int i = 0; i < list.size(); ++i){
        System.out.println(list.get(i).toString());
    }
}

现在,如果您像这样调用该方法:someObject.foo(空);当它试图访问列表时,您可能会得到一个nullPointerException,在本例中是在对list.size()的调用中;现在,您可能永远不会用这样的空值调用someObject.foo(空)。但是,如果方法在生成arraylist时遇到错误(如someobject.foo(otherobject.getarraylist())时返回空值,则可能从该方法中获取arraylist;

当然,如果你这样做,你也会遇到问题:

1
2
ArrayList list = NULL;
list.size();

现在,在目标C中,我们有等效方法:

1
2
3
4
5
6
7
- (void)foo:(NSArray*)anArray
{
    int i;
    for(i = 0; i < [anArray count]; ++i){
        NSLog(@"%@", [[anArray objectAtIndex:i] stringValue];
    }
}

现在,如果我们有以下代码:

1
[someObject foo:nil];

我们有相同的情况,Java将产生一个Null PoExtExchange。nil对象将首先在[anarray count]处访问,但是,不是抛出nullpointerException,Objective-C将根据上面的规则简单地返回0,因此循环将不会运行。但是,如果我们将循环设置为运行设置的次数,那么我们将首先在[Anarray ObjectAtindex:i]向Anarray发送一条消息;这也将返回0,但由于ObjectAtindex:返回一个指针,并且指向0的指针为nil/null,因此每次通过循环时,nslog都将为nil。(尽管nslog是一个函数而不是一个方法,但如果传递了nil nsstring,它将输出(空)。

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


发送给nil的消息不起作用,返回nilnilNULL00.0


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

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

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

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

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


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

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

1
[someNullNSArrayReference count] => 0

也就是说,nil似乎是空数组。隐藏nil nsview引用不起任何作用。方便,嗯?


从Greg Parker的网站:

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

1
2
3
4
5
6
Messages to nil with return type | return
Integers up to 64 bits           | 0
Floating-point up to long double | 0.0
Pointers                         | nil
Structs                          | {0}
Any _Complex type                | {0, 0}

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

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发送消息会使代码更简单——您不必在任何地方检查空值。规范示例可能是访问器方法:

1
2
3
4
5
6
- (void)setValue:(MyClass *)newValue {
    if (value != newValue) {
        [value release];
        value = [newValue retain];
    }
}

如果向nil发送消息是无效的,那么这种方法就更复杂了——在发送消息之前,您需要另外进行两次检查,以确保valuenewValue不是nil

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

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

这段代码同样不需要检查nil值,并且自然流动…

尽管如此,能够向nil发送消息的额外灵活性还是要付出一定代价的。在某些阶段,您可能会编写以特殊方式失败的代码,因为您没有考虑值可能是nil的可能性。


这意味着,为了安全,通常不必检查任何地方是否有任何物体,尤其是:

1
[someVariable release];

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

1
if ( [myString length] > 0 )

或者:

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


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

我发布了一个类似的,但更长的回答,回答了相关的问题"是否断言在目标C中每个对象的创建都是必要的?"如果你想要更多的细节。


发送到nil且返回值的大小大于sizeof(void*)的objc消息在PowerPC处理器上生成未定义的值。除此之外,这些消息还会导致未定义的值返回到英特尔处理器上大小大于8字节的结构字段中。文森特·盖博在他的博客中很好地描述了这一点。


别想"接收器是零的";我同意,这很奇怪。如果你要向nil发送消息,就没有接收者。你只是在给什么都不发信息。

如何处理这是Java和Objtovi-C之间的哲学区别:在Java中,这是一个错误;在Objto-C中,它是NO-OP。


对于基元值,C不表示0,对于指针(在指针上下文中相当于0),则表示空。

Objective-C建立在C的无表示基础上,加上nil。nil是指向Nothing的对象指针。尽管在语义上不同于空值,但它们在技术上是等价的。

新分配的nsobjects的内容设置为0开始生命。这意味着对象必须指向其他对象的所有指针都以nil开头,因此不需要在in it方法中设置self.(association)=nil。

不过,nil最显著的行为是可以向其发送消息。

在其他语言中,如C++(或Java),这会使程序崩溃,但在ObjtoVc中,调用nIL的方法返回零值。这大大简化了表达式,因为它不需要在执行任何操作之前检查nil:

1
2
3
4
5
// For example, this expression...
if (name != nil && [name isEqualToString:@"Steve"]) { ... }

// ...can be simplified to:
if ([name isEqualToString:@"Steve"]) { ... }

意识到nil在objective-c中的工作方式,可以将这种便利性作为一种特性,而不是应用程序中潜伏的bug。确保防止出现不需要nil值的情况,方法是检查并提前返回以静默失败,或者添加nsParameterAssert以引发异常。

来源:http://nshipster.com/nil/无/https://developer.apple.com/library/ios/documentation/cococa/conceptive/objectec/chapters/ocObjectsClasses.html(发送消息至nil)。