所有选择器都是唯一的——无论是在编译时,还是动态的,在运行时通过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)。接下来(忽略标记的指针)是在someObject
s 类中查找选择器(isa
实际上是指针),找到实现并调用它(使用尾调用优化)。
找到实现的东西必须很快,并且要让它真正快速,你希望找到方法实现的关键尽可能快和稳定。为此,您希望密钥可直接使用并且对流程全局唯一。
因此,选择器是唯一的。
它也恰好节省内存是一个非常好的好处,但是如果消息传递速度可以提高 2 倍(但不是 10 倍 2 倍 - 甚至 2 倍内存 2 倍速度 - 而速度是关键,内存使用也很关键,当然)。
如果您真的想深入了解其objc_msgSend()
工作原理,我写了一些指南。请注意,由于它是在标记指针、块作为实现和 ARC 被公开之前编写的,所以它有点过时了。我应该更新文章。