Objective-C是否使用短路求值?

18

我尝试了以下代码:

if(myString != nil && myString.length) { ... }

然后报错:

-[NSNull length]: 无法识别的选择器发送到实例

Objective-C在第一个条件失败后不会短路吗?


4
不对。NSNull和nil并不是相同的东西,完全不一样。 - bbum
4个回答

30

Objective-C支持短路求值,就像C语言一样。

在您的示例中,似乎myStringNSNull而不是nil,因此myString != nil为true。

NSNull是单例,用于表示仅允许对象的空值,例如在NSArray中使用的nil

顺便说一句,通常人们会写if (!myString && myString.length == 0)。与nil比较相当丑陋。另外,我会将长度与0进行比较,这似乎更清晰。


7
我不会说人们“通常”不会与nil进行比较。我已经看到以相等的频率使用两种方式。争论的重点在于,你可以将其解读为“如果myString不是nil”,而不是“如果不是myString”。 - bobDevil
实际上,你是错误的。通过(nil == variable)比较nil是确保不向nil对象发送消息的正确方式。 - Matt Hudson
@Inturbidus:我不确定我理解你的意思。如果你认为应该使用if (nil == myString)而不是if (!myString),那么你是错误的。nil被定义为0,因此这两个比较是完全相同的。 - Georg Schölly
你说得对,nil只是int 0的包装器,但直接引用O'Reilly iOS 4书第45页的话,更好的做法是使用nil != mystring来防止编译器允许mystring = nil的错误,这种错误虽然可以编译通过,但会导致崩溃。我们都使用(!mystring),但在适当的情况下应该教授良好的实践方法。 - Matt Hudson
2
从技术上讲,你可以只写 if(myString.length) {...} 就行了,因为如果 myString 是一个 nil 对象并且你向它发送一个 length 消息,你最终也会得到 nil。 - Matt Mc
显示剩余2条评论

10

Objective-C是C语言的一个严格超集。

由于C语言支持短路求值,因此Objective-C也支持。


1
NString* str = expressionThatReturnsStrOrNil() || @""; 这段代码无法正常工作。这是短路求值未能正常工作的原因。 - Pedro Rolo
1
@PedroMorteRolo:当然可以工作。你期望什么结果?(请记住,||运算符的结果只能是10,不可能是其他任何值)。 - Stephen Canon
1
你是对的。我陷入了C语言和Ruby语言具有相同语义的错觉中。 - Pedro Rolo

3

NSNull是什么?如果它是一个代表“无”的对象,那么它就不会是nil。换句话说,NSNull和nil并不相同。


0

如果你在某个地方使用了NSNull,那么你可能正在使用JSON解析器或CoreData。当CoreData中的数字未设置时,CoreData会返回NSNull - 可能对于CoreData中的NSString值也是如此。

同样,你可以在从服务器返回的JSON中有空元素,并且一些解析器将其作为NSNull对象提供给您。因此,在这两种情况下,您在使用值时必须小心,因为您认为是NSString或NSNumber对象的东西实际上是NSNull。

一种解决方案是在NSNull上定义一个类别,该类别简单地忽略发送到对象的所有非理解消息,如下面的代码所示。然后,您拥有的代码将起作用,因为NSNull.length将返回0。您可以在项目.pch文件中包含类似于此类的内容,该文件包含在项目中的每个文件中。

// NSNull+IgnoreMessages.h
@interface NSNull(IgnoreMessages) 
- (void)forwardInvocation:(NSInvocation *)anInvocation;
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector;
@end

//NSNull+IgnoreMessages.m
#import "NSNull+IgnoreMessages.h"
@implementation NSNull(IgnoreMessages)
- (void)forwardInvocation:(NSInvocation *)anInvocation
{
    if ( [self respondsToSelector:[anInvocation selector]] )
      [anInvocation invokeWithTarget:self];
}

- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
{
    NSMethodSignature *sig=[[NSNull class] instanceMethodSignatureForSelector:aSelector];
        // Just return some meaningless signature
    if(sig==nil)
      sig=[NSMethodSignature signatureWithObjCTypes:"@^v^c"];

    return sig;
}
@end

这只是等待失败的结果。重要的是要知道何时处理NSNull而不是nil,这样可以减少代码出错的可能性。在问题的示例中,有一个包含NSNull的错误消息,但使用您的代码,您将看不到该错误,并且if(myString)仍将评估为true。 - Georg Schölly
告诉我,如果您可以向它发送任何消息,就像nil一样,它怎么会失败呢?实际上,它仍然会调用NSNull实际理解的任何消息(包括所有NSObject方法)。现在它可能会导致错误,因为如果您明确检查与nil的等价性,那将不成立。但在大多数编码中,正是因为nil可以发送任何消息,所以我只测试是否为(myString.length> 0),这在这里运行良好。实际上,此代码将防止NSNull意外进入您未预期的位置时可能导致代码崩溃,因此总体而言,这是稳定性的提高。 - Kendall Helmstetter Gelner
为什么不直接使用 NSMethodSignature *sig=[self methodSignatureForSelector:aSelector]; 呢? - user102008
@user:我得检查一下确保结果一样,但这似乎是个好主意。 - Kendall Helmstetter Gelner
@Georg:在JSON的情况下,您正在向用户掩盖一个错误。也就是说,在应用程序发货很久之后,NSNull可能会滑入数据源,并且它将引发异常。而使用此类别,用户只会看到某个空白字段。如果您真的担心在测试中掩盖它,您始终可以仅在发布版本中包含此类别(尽管我的建议始终是:测试正在发布的内容)。 - Kendall Helmstetter Gelner
显示剩余2条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接