为什么 Objective-C 方法名的最后一部分必须有一个参数(当有多个部分时) ?

在 Objective-C 中,如果最后一个组件没有参数,就不能声明方法名。例如,以下内容是非法的。

-(void)take:(id)theMoney andRun;
-(void)take:(id)yourMedicine andDontComplain;

为什么 Objective-C 被设计成这样?是不是只是 Smalltalk 的一个神器,没人觉得有必要处理掉?

这种限制在 Smalltalk 中是有意义的,因为 Smalltalk 在消息调用周围没有分隔符,所以最后一个组件将被解释为最后一个参数的一元消息。例如,BillyAndBobby take:'$100' andRun将被解析为 BillyAndBobby take:('$100' andRun)。在 Objective-C,这无关紧要,因为那里需要方括号。

支持无参数选择器组件不会在所有通常的语言度量方式中获得太多,因为程序员选择的方法名称(例如 runWith:而不是 take:andRun)不会影响程序的函数语义,也不会影响语言的表达能力。实际上,具有无参数组件的程序相当于没有参数组件的程序。因此,我对声明这样一个特性是不必要的答案不感兴趣(除非这是 Objective-C 设计者所陈述的原因; 是否有人碰巧认识 Brad Cox 或 Tom Love?他们在这里吗?)或者说如何编写方法名,这样就不需要这个特性了。主要的好处是可读性和可写性(就像可读性一样,只是... ... 你知道的) ,因为这意味着你可以写出更接近自然语言句子的方法名。像 -(BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication*)theApplication这样的(Matt Gallagher 指出,当您删除形式参数时,“爱的可可”有点令人困惑)可以命名为 -(BOOL)application:(NSApplication*)theApplication shouldTerminateAfterLastWindowClosed,因此将参数紧挨着适当的名词。

例如,Apple 的 Objective-C 运行时完全能够处理这类选择器,那么为什么编译器不能呢?为什么不在方法名中也支持它们呢?

#import <Foundation/Foundation.h>
#import <objc/runtime.h>


@interface Potrzebie : NSObject
-(void)take:(id)thing;
@end


@implementation Potrzebie
+(void)initialize {
SEL take_andRun = NSSelectorFromString(@"take:andRun");
IMP take_ = class_getMethodImplementation(self, @selector(take:));
if (take_) {
if (NO == class_addMethod(self, take_andRun, take_, "@@:@")) {
NSLog(@"Couldn't add selector '%@' to class %s.",
NSStringFromSelector(take_andRun),
class_getName(self));
}
} else {
NSLog(@"Couldn't find method 'take:'.");
}
}


-(void)take:(id)thing {
NSLog(@"-take: (actually %@) %@",NSStringFromSelector(_cmd), thing);
}
@end


int main() {
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];


Potrzebie *axolotl=[[Potrzebie alloc] init];
[axolotl take:@"paradichloroaminobenzaldehyde"];
[axolotl performSelector:NSSelectorFromString(@"take:andRun")
withObject:@"$100"];
[axolotl release];


[pool release];
return 0;
}
10460 次浏览

I assume they are not supported in Objective-C because they weren't available in Smalltalk, either. But that has a different reason than you think: they are not needed. What is needed is support for methods with 0, 1, 2, 3, ... arguments. For every number of arguments, there is already a working syntax to call them. Adding any other syntax would just cause unnecessary confusion.

If you wanted multi-word parameterless selectors, why stop with a single extra word? One might then ask that

 [axolotl perform selector: Y with object: Y]

also becomes supported (i.e. that a selector is a sequence of words, some with colon and a parameter, and others not). While this would have been possible, I assume that nobody considered it worthwhile.

21 years of programming Objective-C and this question has never crossed my mind. Given the language design, the compiler is right and the runtime functions are wrong ().

The notion of interleaved arguments with method names has always meant that, if there is at least one argument, the last argument is always the last part of the method invocation syntax.

Without thinking it through terribly much, I'd bet there are some syntactic bugaboos with not enforcing the current pattern. At the least, it would make the compiler harder to write in that any syntax which has optional elements interleaved with expressions is always harder to parse. There might even be an edge case that flat out prevents it. Certainly, Obj-C++ would make it more challenging, but that wasn't integrated with the language until years after the base syntax was already set in stone.

As far as why Objective-C was designed this way, I'd suspect the answer is that the original designers of the language just didn't consider allowing the interleaved syntax to go beyond that last argument.

That is a best guess. I'll ask one of 'em and update my answer when I find out more.


I asked Brad Cox about this and he was very generous in responding in detail (Thanks, Brad!!):

I was focused at that time on duplicating as much of Smalltalk as possible in C and doing that as efficiently as possible. Any spare cycles went into making ordinary messaging fast. There was no thought of a specialized messaging option ("reallyFast?" [bbum: I asked using 'doSomething:withSomething:reallyFast' as the example]) since ordinary messages were already as fast as they could be. This involved hand-tuning the assembler output of the C proto-messager, which was such a portability nightmare that some if not all of that was later taken out. I do recall the hand-hacked messager was very fast; about the cost of two function calls; one to get into the messager logic and the rest for doing method lookups once there.
Static typing enhancements were later added on top of Smalltalk's pure dynamic typing by Steve Naroff and others. I had only limited involvement in that.

Go read Brad's answer!

Just for your information, the runtime doesn't actually care about the selectors, any C string is valid, you could as well make a selector like that: "==+===+---__--¨¨¨¨¨^::::::" with no argument the runtime will accept it, the compiler just can't or else it's impossible to parse. There are absolutely no sanity check when it comes to selectors.

This is Brad Cox. My original answer misunderstood the question. I assumed reallyFast was a hardcoded extension to trigger faster messaging, not a kind of syntactic sugar. The real answer is that Smalltalk didn't support it, perhaps because its parser couldn't deal with the (assumed) ambiguity. Although OC's square brackets would remove any ambiguity, I simply didn't think of departing from Smalltalk's keyword structure.