所有选择器都是唯一的——无论是在编译时还是在运行时动态地通过sel_getUid() 或首选sel_registerName()(后者在很大程度上是首选,前者由于历史原因仍然存在)-- 速度。
背景故事:要调用一个方法,运行时需要一个选择器来标识要调用的内容和将要调用的对象。这就是为什么 Objective-C 中的每个方法调用都有两个参数:显而易见的众所周知的self 和不可见的隐含参数_cmd。 _cmd 是当前执行的方法的 SEL。也就是说,您可以将此代码粘贴到任何方法中,以查看当前执行方法的名称——选择器:
NSLog(@"%@", NSStringFromSelector(_cmd));
注意_cmd 不是一个全局的;这确实是您方法的一个论据。见下文。
通过使选择器唯一化,所有基于选择器的操作都使用指针相等测试来实现,而不是字符串处理或任何指针取消引用。
特别是,每次进行方法调用时:
[someObject doSomething: toThis withOptions: flags]; // calls SEL doSomething:withOptions:
编译器生成此代码(或密切相关的变体):
objc_msgSend(someObject, @selector(doSomething:withOptions:), toThis, flags);
objc_msgSend() 做的第一件事是检查someObject 是否为 nil,如果是则短路 (nil-eats-message)。接下来(忽略标记的指针)是在someObjects 类中查找选择器(实际上是isa 指针),找到实现,然后调用它(使用尾调用优化)。
找到实现的事情必须很快,并且要让它真正快速,你希望找到方法实现的关键尽可能快速和稳定。要做到该,您希望密钥可直接使用且对流程全局唯一。
因此,选择器是唯一的。
它也恰好可以节省内存是一个非常好的好处,但是如果消息传递速度可以提高 2 倍(但不是 2 倍速度的 10 倍,甚至 2 倍速度的 2 倍内存),Messenger 将使用比现在更多的内存 -虽然速度很关键,但内存使用当然也很关键)。
如果您真的想深入了解objc_msgSend() 的工作原理,我写了一个bit of a guide。请注意,由于它是在标记指针、块作为实现和 ARC 被公开之前编写的,所以它有点过时了。我应该更新文章。