为什么将任何选择器发送到Nil对象什么都不做,但是向任何NSObject发送"无效"选择器会引发异常?

Mik*_*gue 11 objective-c

有谁知道为什么NextStep/Apple决定采用"方便的方法",在传递Nil对象时不做任何事情,但是在将实例化对象传递给无效选择器时引发异常的"Java方法"?

例如,

// This does "nothing"
NSObject *object = Nil;
[object thisDoesNothing];

object = [[NSObject alloc] init];
// This causes an NSInvalidArgumentException to be raised
[object thisThrowsAnException];
Run Code Online (Sandbox Code Playgroud)

所以一方面,我们有方便不必检查Nil(假设我们不太关心方法调用的结果) - 但另一方面,如果我们的对象,我们必须检查异常没有回应方法?

如果我不确定该对象是否会响应,我要么:

@try {
    [object thisThrowsAnException];
} @catch (NSException *e){
    // do something different with object, since we can't call thisThrowsAnException
}
Run Code Online (Sandbox Code Playgroud)

要么,

if([object respondsToSelector:@selector(thisThrowsAnException)]) {
    [object thisThrowsAnException];
}
else {
    // do something different with object, since we can't call thisThrowsAnException
}
Run Code Online (Sandbox Code Playgroud)

(后者可能是更好的方法,因为如果object为Nil,则选择器不会引发异常,因此您的代码可能不会按照您希望的方式运行).

我的问题是:为什么Apple决定以这种方式实施它?
为什么不对实例化对象进行无法识别的选择器调用而不引发异常?
或者,如果您尝试在其上调用方法,为什么不让Nil对象引发异常?

rob*_*off 11

我无法完全回答你的问题,但我可以回答部分问题.Objective-C允许您发送消息,nil因为它使代码更加优雅. 你可以在这里阅读这个设计决定,我会窃取它的例子:

假设您想获取某人在办公室电话上拨打的最后一个电话号码.如果你不能发送消息nil,你必须这样写:

Office *office = [somePerson office];
// Person might not have an office, so check it...
if (office) {
    Telephone *phone = [office telephone];
    // The office might not have a telephone, so check it...
    if (phone) {
        NSString *lastNumberDialed = [phone lastNumberDialed];
        // The phone might be brand new, so there might be no last-dialed-number...
        if (lastNumberDialed) {
            // Use the number, for example...
            [myTextField setText:lastNumberDialed];
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

现在假设您可以发送消息nil(并始终nil返回):

NSString *lastNumberDialed = [[[somePerson office] telephone] lastNumberDialed];
if (lastNumberDialed) {
    [myTextField setText:lastNumberDialed];
}
Run Code Online (Sandbox Code Playgroud)

至于为什么向对象发送无法识别的选择器会引发异常:我不确定.我怀疑这是一个bug而不是无害的常见问题.在我的代码中,我只希望在需要发送可选协议消息时(例如向委托发送可选消息)静默忽略无法识别的选择器.所以我希望系统将其视为一个错误,让我在相对罕见的情况下明确表示我不希望它是一个错误.

请注意,您可以通过几种不同的方式修改(在某种程度上)处理您自己的类中无法识别的选择器.看看的forwardingTargetForSelector:,forwardInvocation:,doesNotRecognizeSelector:,和resolveInstanceMethod:方法NSObject.

  • 对.在这种情况下,您首先要检查`respondsToSelector:`,因为它比捕获异常要快得多.当你第一次设置委托(或数据源或其他)时,Apple的类会缓存`respondsToSelector:`的结果. (2认同)

Cod*_*aFi 5

来自好的文档:

\n\n
\n

在 Objective-C 中,向 nil\xe2\x80\x94 发送消息是有效的,但在运行时没有任何作用。

\n
\n\n

至于无法识别的选择器行为的另一个问题,NSObject 的旧实现文件(来自 MySTEP 库)显示罪魁祸首是 NSObject 方法-doesNotRecognizeSelector:,它看起来有点如下:

\n\n
- (void) doesNotRecognizeSelector:(SEL)aSelector\n{\n    [NSException raise:NSInvalidArgumentException\n                format:@"NSObject %@[%@ %@]: selector not recognized", \n                        object_is_instance(self)?@"-":@"+",\n                        NSStringFromClass([self class]), \n                        NSStringFromSelector(aSelector)];\n}\n
Run Code Online (Sandbox Code Playgroud)\n\n

这意味着可以对 ObjC 方法进行修改,这样它们实际上就不必引发错误。这意味着该决定完全是任意的,就像将“方法吃”消息切换为 nil 的决定一样。可以通过方法调配 NSObject 来完成这一壮举(非常危险,因为它会在 mac 上引发 EXC_BAD_ACCESS 或 EXC_I386_BPT,但至少它不会引发异常)

\n\n
void Swizzle(Class c, SEL orig, SEL new)\n{\n    Method origMethod = class_getInstanceMethod(c, orig);\n    Method newMethod = class_getInstanceMethod(c, new);\n    if(class_addMethod(c, orig, method_getImplementation(newMethod), method_getTypeEncoding(newMethod)))\n        class_replaceMethod(c, new, method_getImplementation(origMethod), method_getTypeEncoding(origMethod));\n    else\n        method_exchangeImplementations(origMethod, newMethod);\n}\n\n-(void)example:(id)sender {\n    Swizzle([NSObject class], @selector(doesNotRecognizeSelector:), @selector(description));\n    [self performSelector:@selector(unrecog)];\n}\n
Run Code Online (Sandbox Code Playgroud)\n\n

类别:

\n\n
@implementation NSObject (NoExceptionMessaging)\n\n-(void)doesNotRecognizeSelector:(SEL)aSelector {\n    NSLog(@"I\'ve got them good ol\' no exception blues.");\n}\n@end\n
Run Code Online (Sandbox Code Playgroud)\n