【问题标题】:Understanding uniqueness of selectors in Objective-C了解 Objective-C 中选择器的唯一性
【发布时间】:2012-06-15 13:24:30
【问题描述】:

我在理解“选择器”的部分功能时遇到问题,如 Apple 指南中所述。我已将我感到困惑的部分加粗:

在 Objective-C 中,选择器有两种含义。可以用来参考 只是在源代码消息中使用方法的名称 到一个对象。 不过,它也指的是唯一标识符 编译源代码时替换名称。已编译 选择器的类型为 SEL。 所有同名的方法都有 相同的选择器。 您可以使用选择器来调用 对象——这为实现 Cocoa 中的目标-动作设计模式。

方法和选择器为了提高效率,不使用完整的 ASCII 名称作为 编译代码中的方法选择器。相反,编译器编写每个 方法名放入表中,然后将该名称与唯一标识符配对 表示运行时的方法。 运行时系统确保 每个标识符都是唯一的:没有两个选择器是相同的,并且所有 具有相同名称的方法具有相同的选择器。

谁能解释这些位?另外,如果不同的类有同名的方法,它们也会有相同的选择器吗?

【问题讨论】:

    标签: objective-c selector objective-c-runtime


    【解决方案1】:

    所有选择器都是唯一的——无论是在编译时还是在运行时动态地通过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 被公开之前编写的,所以它有点过时了。我应该更新文章。

    【讨论】:

    • 哇,多么精彩的回放,非常感谢。请你能给我一些提示,我可以像你一样提高我的技能。
    • 不客气;至于如何改进?实践。我这样做已经有一段时间了。 :)
    【解决方案2】:

    是的。类确实共享选择器。

    我可以从源代码objc-sel.mm举个例子,但是当你使用sel_registerUid()(在@selector()幕后使用)时, 它将输入字符串复制到一个内部缓冲区(如果该字符串之前没有注册过),所有未来的 SEL 都指向该缓冲区。

    这样做是为了减少内存使用,并且更容易转发消息。

    【讨论】:

    • 有很多次我想知道你是不是在偷偷做ObjC运行时,这就是其中之一... +1
    • 感谢您的帮助和可爱的回答。
    • @RichardJ.Ross:!No = YES。 ;)
    • 虽然细节正确,但结论忽略了这样做的最重要原因。
    • 不是清晰度问题,而是准确性问题。它是速度,速度速度。查看objc_msgSend() 的缓存查找实现;每一个方法调用——当你启动一个设备时,数以亿计的调用——都依赖于选择器的唯一性,以允许缓存查找和消息分发尽可能快(或团队知道如何进行的尽可能快)它,无论如何:)。
    【解决方案3】:

    不过,它也指的是在编译源代码时替换名称的唯一标识符...所有具有相同名称的方法具有相同的选择器。

    为此,我在选择器上参考了出色的blog post

    对于具有相同名称和参数的所有方法,选择器都是相同的——无论是哪些对象定义了它们,这些对象是否在类层次结构中相关,或者实际上彼此无关。在运行时,Objective-C 进入类并直接询问它,“你是否响应此选择器?”,如果响应,则调用生成的函数指针。

    运行时系统确保每个标识符都是唯一的:没有两个选择器是相同的,所有同名的方法都有相同的选择器。

    以一种搞砸的方式,这是有道理的。如果方法 A 和方法 B 具有完全相同的名称和参数,那么将它们的选择器存储为一个查找并查询接收器而不是在运行时在两个基本相同名称的选择器之间进行决策不是更有效吗?

    【讨论】:

      【解决方案4】:

      看SEL类型,不用定义这个选择器来自哪个类,给它一个方法名即可,例如:

      SEL animationSelector = @selector(addAnimation:forKey:); 
      

      例如,您可以将其想象为街道名称。许多城市可以有相同的街道名称,但没有城市的街道名称毫无价值。选择器也一样,你可以定义一个选择器而不添加它所在的对象。但是如果没有合适的类,它完全没有价值..

      【讨论】:

      • 感谢您的帮助,并通过这个城市街道名称示例帮助我理解这个主题:)。
      猜你喜欢
      • 2010-10-18
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-08-20
      • 1970-01-01
      • 1970-01-01
      • 2013-10-14
      • 2010-10-05
      相关资源
      最近更新 更多