【问题标题】:Sending a message to nil in Objective-C在 Objective-C 中向 nil 发送消息
【发布时间】:2010-09-14 11:00:22
【问题描述】:

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

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

  • 如果方法返回一个对象,任何指针类型,任何整数标量 大小小于或等于 sizeof(void*)、浮点数、双精度数、 long double, 或 long long, then a 发送到 nil 的消息返回 0。
  • 如果方法返回一个结构,由 Mac OS X ABI 函数定义 调用指南返回 注册,然后向 nil 发送一条消息 为每个字段返回 0.0 数据结构。其他结构数据 类型不会用零填充。
  • 如果方法返回的不是上述值 类型消息的返回值 发送到 nil 是未定义的。

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

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

【问题讨论】:

  • 我也有Java背景,一开始我被这个好功能吓到了,但现在我觉得它非常可爱!;
  • 谢谢,这是个好问题。你有没有看透它的好处?它给我的印象是“不是错误,而是功能”。我不断遇到错误,Java 只会给我一个例外,所以我知道问题出在哪里。我不乐意用空指针异常来在这里和那里保存一两行琐碎的代码。

标签: objective-c


【解决方案1】:

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

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

[someNullNSArrayReference count] => 0

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

【讨论】:

    【解决方案2】:

    嗯,我认为可以用一个非常人为的例子来描述它。假设您在 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());

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

    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/零结构时不会中断的方法,那么您就不必检查以确保对象或参数为 nil。

    【讨论】:

    • 可能值得一提的是,在过去的几十年里,这种行为一直是 Objective-C 社区中很多争论的主题。不同的人对“安全”和“便利”之间的权衡有不同的评价。
    • 在实践中,将消息传递给 nil 与 Objective-C 的工作方式之间存在很多对称性,尤其是在 ARC 中新的弱指针功能中。弱指针自动归零。所以设计你的 API 让它可以响应 0/nil/NIL/NULL 等。
    • 我认为如果你这样做 myObject-&gt;iVar 它会崩溃,无论它是 C with 还是 without 对象。 (抱歉挖坟。)
    • @11684 没错,但-&gt; 不再是 Objective-C 操作,而是非常通用的 C-ism。
    • 由于 obj-c 的 Nil 消息传递,所有用户(不仅仅是管理员)都可以访问 recent OSX root exploit/hidden backdoor api
    【解决方案3】:

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

    [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)”
    • 这是一个怎样的错误? [myString length] 如果 myString 为 nil,则返回零(nil)...我认为可能存在问题的一件事是 [myView frame],如果 myView 为 nil,我认为它会给你一些古怪的东西。
    • 如果您围绕默认值(0、nil、NO)意味着“无用”的概念设计类和方法,这是一个强大的工具。在检查长度之前,我永远不必检查我的字符串是否为零。对我来说,当我处理文本时,空字符串是无用的,而且是零字符串。我也是一名 Java 开发人员,我知道 Java 纯粹主义者会避开这个,但它可以节省大量编码。
    【解决方案4】:

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

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

    【讨论】:

    • 在java中该行为有一个例外,如果您在null上调用静态函数,则相当于在变量的编译时类上调用该函数(是否无关紧要)空)。
    【解决方案5】:

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

    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 的可能性。

    【讨论】:

      【解决方案6】:

      发给nil 的消息什么都不做,而是返回nilNilNULL00.0

      【讨论】:

        【解决方案7】:

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

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

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

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

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

        【讨论】:

          【解决方案8】:

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

          中很好地描述了这一点

          【讨论】:

            【解决方案9】:

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

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

            【讨论】:

            • 我从来没有这样想过。这似乎是一个非常方便的功能。
            • 不错的猜测,但从历史上看,为什么做出这个决定并不准确。异常处理从一开始就存在于该语言中,尽管与现代习语相比,最初的异常处理程序相当原始。 Nil-eats-message 是一种有意识的设计选择,源于 Smalltalk 中 Nil 对象的 optional 行为。在设计最初的 NeXTSTEP API 时,方法链非常普遍,nil 返回经常用于将链短路成 NO-op。
            【解决方案10】:

            来自Greg Parkersite

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

            返回类型为 nil 的消息 |返回 高达 64 位的整数 | 0 浮点数高达 long double | 0.0 指针 |零 结构 | {0} 任何_复杂类型 | {0, 0}

            【讨论】:

              【解决方案11】:

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

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

              新分配的 NSObject 开始生命时其内容设置为 0。这意味着该对象指向其他对象的所有指针都以 nil 开头,因此没有必要设置 self.(association) = nil in初始化方法。

              不过,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 发送消息)。

              【讨论】:

                猜你喜欢
                • 1970-01-01
                • 2011-07-26
                • 2011-11-05
                • 2010-10-29
                • 2013-08-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 2012-08-06
                相关资源
                最近更新 更多