在 Objective-C 中向 nil 发送消息

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/156395/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-03 20:47:33  来源:igfitidea点击:

Sending a message to nil in Objective-C

objective-c

提问by Ryan Delucchi

As a Java developer who is reading Apple's Objective-C 2.0 documentation: I wonder what "sending a message to nil" means - let alone how it is actually useful. Taking an excerpt from the documentation:

作为一名正在阅读 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.

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

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

Has Java rendered my brain incapable of grokking the explanation above? Or is there something that I am missing that would make this as clear as glass?

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

I do get the idea of messages/receivers in Objective-C, I am simply confused about a receiver that happens to be nil.

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

采纳答案by Michael Buckley

Well, I think it can be described using a very contrived example. Let's say you have a method in Java which prints out all of the elements in an ArrayList:

嗯,我认为可以用一个非常人为的例子来描述。假设您在 Java 中有一个方法可以打印出 ArrayList 中的所有元素:

void foo(ArrayList list)
{
    for(int i = 0; i < list.size(); ++i){
        System.out.println(list.get(i).toString());
    }
}

Now, if you call that method like so: someObject.foo(NULL); you're going to probably get a NullPointerException when it tries to access list, in this case in the call to list.size(); Now, you'd probably never call someObject.foo(NULL) with the NULL value like that. However, you may have gotten your ArrayList from a method which returns NULL if it runs into some error generating the ArrayList like someObject.foo(otherObject.getArrayList());

现在,如果您像这样调用该方法: someObject.foo(NULL); 当它尝试访问列表时,你可能会得到一个 NullPointerException,在这种情况下是在调用 list.size(); 现在,您可能永远不会像这样使用 NULL 值调用 someObject.foo(NULL) 。但是,您可能已经从一个返回 NULL 的方法中获取了 ArrayList,如果它在生成 ArrayList 时遇到一些错误,例如 someObject.foo(otherObject.getArrayList());

Of course, you'll also have problems if you do something like this:

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

ArrayList list = NULL;
list.size();

Now, in Objective-C, we have the equivalent method:

现在,在 Objective-C 中,我们有等效的方法:

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

Now, if we have the following code:

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

[someObject foo:nil];

we have the same situation in which Java will produce a NullPointerException. The nil object will be accessed first at [anArray count] However, instead of throwing a NullPointerException, Objective-C will simply return 0 in accordance with the rules above, so the loop will not run. However, if we set the loop to run a set number of times, then we're first sending a message to anArray at [anArray objectAtIndex:i]; This will also return 0, but since objectAtIndex: returns a pointer, and a pointer to 0 is nil/NULL, NSLog will be passed nil each time through the loop. (Although NSLog is a function and not a method, it prints out (null) if passed a nil NSString.

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

In some cases it's nicer to have a NullPointerException, since you can tell right away that something is wrong with the program, but unless you catch the exception, the program will crash. (In C, trying to dereference NULL in this way causes the program to crash.) In Objective-C, it instead just causes possibly incorrect run-time behavior. However, if you have a method that doesn't break if it returns 0/nil/NULL/a zeroed struct, then this saves you from having to check to make sure the object or parameters are nil.

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

回答by Peter Hosey

A message to nildoes nothing and returns nil, Nil, NULL, 0, or 0.0.

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

回答by Joe McMahon

All of the other posts are correct, but maybe it's the concept that's the thing important here.

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

In Objective-C method calls, any object reference that can accept a selector is a valid target for that selector.

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

This saves a LOT of "is the target object of type X?" code - as long as the receiving object implements the selector, it makes absolutely no differencewhat class it is! nilis an NSObject that accepts any selector - it just doesn't doanything. This eliminates a lot of "check for nil, don't send the message if true" code as well. (The "if it accepts it, it implements it" concept is also what allows you to create protocols, which are sorta kinda like Java interfaces: a declaration that if a class implements the stated methods, then it conforms to the protocol.)

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

The reason for this is to eliminate monkey code that doesn't do anything except keep the compiler happy. Yes, you get the overhead of one more method call, but you save programmer time, which is a far more expensive resource than CPU time. In addition, you're eliminating more code and more conditional complexity from your application.

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

Clarifying for downvoters: you may think this is not a good way to go, but it's how the language is implemented, and it's the recommended programming idiom in Objective-C(see the Stanford iPhone programming lectures).

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

回答by Rich

What it means is that the runtime doesn't produce an error when objc_msgSend is called on the nil pointer; instead it returns some (often useful) value. Messages that might have a side effect do nothing.

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

It's useful because most of the default values are more appropriate than an error. For example:

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

[someNullNSArrayReference count] => 0

I.e., nil appears to be the empty array. Hiding a nil NSView reference does nothing. Handy, eh?

即, nil 似乎是空数组。隐藏一个 nil NSView 引用没有任何作用。好用吧?

回答by Heath Borders

From Greg Parker's site:

格雷格帕克网站

If running LLVM Compiler 3.0 (Xcode 4.2) or later

如果运行 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}

回答by mmalc

In the quotation from the documentation, there are two separate concepts -- perhaps it might be better if the documentation made that more clear:

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

There are several patterns in Cocoa that take advantage of this fact.

The value returned from a message to nil may also be valid:

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

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

The former is probably more relevant here: typically being able to send messages to nilmakes code more straightforward -- you don't have to check for null values everywhere. The canonical example is probably the accessor method:

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

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

If sending messages to nilwere not valid, this method would be more complex -- you'd have to have two additional checks to ensure valueand newValueare not nilbefore sending them messages.

如果将消息发送到nil无效,则此方法将更加复杂 -在向它们发送消息之前,您必须进行两项额外检查以确保valuenewValue无效nil

The latter point (that values returned from a message to nilare also typically valid), though, adds a multiplier effect to the former. For example:

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

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

This code again doesn't require a check for nilvalues, and flows naturally...

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

All this said, the additional flexibility that being able to send messages to nildoes come at some cost. There is the possibility that you will at some stage write code that fails in a peculiar way because you didn't take into account the possibility that a value might be nil.

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

回答by Kendall Helmstetter Gelner

It means often not having to check for nil objects everywhere for safety - particularly:

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

[someVariable release];

or, as noted, various count and length methods all return 0 when you've got a nil value, so you do not have to add extra checks for nil all over:

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

if ( [myString length] > 0 )

or this:

或这个:

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

回答by benzado

Don't think about "the receiver being nil"; I agree, that ispretty weird. If you're sending a message to nil, there is no receiver. You're just sending a message to nothing.

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

How to deal with that is a philosophical difference between Java and Objective-C: in Java, that's an error; in Objective-C, it is a no-op.

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

回答by Nikita Zhuk

ObjC messages which are sent to nil and whose return values have size larger than sizeof(void*) produce undefined values on PowerPC processors. In addition to that, these messages cause undefined values to be returned in fields of structs whose size is larger than 8 bytes on Intel processors as well. Vincent Gable has described this nicely in his blog post

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

回答by Rinzwind

I don't think any of the other answers have mentioned this clearly: if you're used to Java, you should keep in mind that while Objective-C on Mac OS X has exception handling support, it's an optional language feature that can be turned on/off with a compiler flag. My guess is that this design of "sending messages to nilis safe" predates the inclusion of exception handling support in the language and was done with a similar goal in mind: methods can return nilto indicate errors, and since sending a message to nilusually returns nilin turn, this allows the error indication to propagate through your code so you don't have to check for it at every single message. You only have to check for it at points where it matters. I personally think exception propagation&handling is a better way to address this goal, but not everyone may agree with that. (On the other hand, I for example don't like Java's requirement on you having to declare what exceptions a method may throw, which often forces you to syntacticallypropagate exception declarations throughout your code; but that's another discussion.)

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

I've posted a similar, but longer, answer to the related question "Is asserting that every object creation succeeded necessary in Objective C?"if you want more details.

我已经发布了一个类似但更长的相关问题的答案“在目标 C 中是否有必要断言每个对象创建都成功?” 如果你想了解更多细节。