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

时间:2008-10-01 06:00:47

标签: objective-c

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

  

Cocoa有几种模式   利用这个事实。该   从消息返回的值为nil   也可能有效:

     
      
  • 如果方法返回一个对象,任何指针类型,任何整数标量   大小小于或等于   sizeof(void *),float,double,a   长一倍,或长一长,然后一个   发送给nil的消息返回0。
  •   
  • 如果方法返回结构,则由Mac OS X ABI函数定义   呼叫指南将被退回   寄存器,然后发送给nil的消息   为每个字段返回0.0   数据结构。其他结构数据   类型不会用零填充。
  •   
  • 如果方法返回上述值以外的任何值   键入消息的返回值   发送到nil是未定义的。
  •   

Java是否让我的大脑无法解释上面的解释?或者是否有一些我想念的东西会让它像玻璃一样清晰?

我确实了解了Objective-C中的消息/接收器,我只是对接收器恰好是nil感到困惑。

11 个答案:

答案 0 :(得分:91)

嗯,我认为可以用一个非常人为的例子来描述。假设您在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)。但是,如果遇到某些生成ArrayList的错误(例如someObject.foo(otherObject.getArrayList());您可能已从一个返回NULL的方法获取了ArrayList;

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

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,它会打印出来(null)。

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

答案 1 :(得分:51)

发送给nil的邮件不执行任何操作,并返回nilNilNULL00.0

答案 2 :(得分:40)

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

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

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

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

澄清downvoters:你可能认为这不是一个好方法,但它是语言的实现方式,而且是Objective-C 中推荐的编程习惯(参见Stanford iPhone编程)讲座)。

答案 3 :(得分:16)

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

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

[someNullNSArrayReference count] => 0

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

答案 4 :(得分:12)

在文档的引文中,有两个独立的概念 - 如果文档更清晰可能会更好:

  

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

     

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

前者可能在这里更相关:通常能够向nil发送消息使代码更直接 - 您不必在任何地方检查空值。规范示例可能是存取方法:

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

如果向nil发送邮件无效,则此方法会更复杂 - 您必须再进行两项检查以确保valuenewValue不是{{ 1}}在发送消息之前。

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

nil

此代码再次不需要检查if ([myArray count] > 0) { // do something... } 值,并自然流动......

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

答案 5 :(得分:12)

来自Greg Parkersite

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

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}

答案 6 :(得分:9)

这意味着通常无需在任何地方检查零物品以确保安全 - 特别是:

[someVariable release];

或者,如上所述,当你有一个nil值时,各种count和length方法都会返回0,所以你不需要为nil添加额外的nil检查:

if ( [myString length] > 0 )

或者这个:

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

答案 7 :(得分:6)

不要考虑“接收者是零”;我同意, 非常奇怪。如果您要向nil发送消息,则没有接收方。你只是发送消息。

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

答案 8 :(得分:6)

发送到nil并且其返回值的大小大于sizeof(void *)的ObjC消息在PowerPC处理器上产生未定义的值。除此之外,这些消息还会导致在Intel处理器上大小超过8个字节的结构字段中返回未定义的值。 Vincent Gable在他的blog post

中很好地描述了这一点

答案 9 :(得分:6)

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

如果您需要更多详细信息,我已对相关问题"Is asserting that every object creation succeeded necessary in Objective C?"发布了类似但更长的答案。

答案 10 :(得分:2)

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

Objective-C通过添加nil来构建C的无表示。 nil是一个没有任何东西的对象指针。虽然在语义上与NULL不同,但它们在技术上彼此相同。

newly-alloc'd NSObjects以其内容设置为0开始生命。这意味着对象对其他对象的所有指针都以nil开头,因此没有必要,例如,设置self。(association)= nil in init方法。

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中工作,这使得这种便利成为一种功能,而不是应用程序中潜伏的错误。确保通过检查并提前返回静默失败,或者添加NSParameterAssert以引发异常来防止nil值不需要的情况。

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