Objective C消息调度机制

Dol*_*hin 59 performance objective-c

我只是盯着玩Objective C(编写玩具iPhone应用程序),我很好奇用于发送消息的底层机制.我非常了解C++中的虚函数通常是如何实现的,以及相对于静态或非虚方法调用的成本是多少,但我没有任何Obj-C背景知道如何发送消息.浏览我发现这个松散的基准测试,它提到IMP缓存的消息比虚函数调用更快,而虚拟函数调用反过来比标准消息发送更快.

我不是要优化任何事情,只是更深入地了解消息的分派方式.

  • 如何发送Obj-C消息?
  • 实例方法指针如何被缓存,你是否(通常)通过阅读代码告诉消息是否会被缓存?
  • 类方法本质上与C函数(或C++中的静态类方法)相同,还是有更多的东西?

我知道其中一些问题可能是"依赖于实现的",但实际上只有一个实现非常重要.

Ale*_*ski 91

如何发送Obj-C消息?

使用运行时的objc_msgSend()函数调度Objective-C消息.在Apple文档中显示,该函数至少需要2个参数:

  1. 接收对象
  2. 消息的选择器
  3. [正在发送的消息的参数的变量列表.]

类的实例有一个isa指针,它是指向其类对象的指针.每个对象中的方法选择器存储在类对象的"表"中,objc_msgSend()函数跟随isa指向类对象的指针,查找此表,并检查该方法是否在类的表中.如果找不到它,它会在类的超类表中查找该方法.如果未找到,它将继续向上对象树,直到它找到方法或到达根对象(NSObject).此时,抛出异常.

实例方法指针如何被缓存,你是否(通常)通过阅读代码告诉消息是否会被缓存?

来自Apple关于消息传递的Objective-C运行时指南:

为了加速消息传递过程,运行时系统在使用它们时缓存方法的选择器和地址.每个类都有一个单独的缓存,它可以包含继承方法的选择器以及类中定义的方法.在搜索调度表之前,消息传递例程首先检查接收对象类的高速缓存(理论上可能会再次使用一次使用的方法).如果方法选择器位于缓存中,则消息传递仅比函数调用稍慢.一旦程序运行了足够长的时间来"预热"其缓存,它发送的几乎所有消息都会找到一个缓存的方法.随着程序的运行,缓存会动态增长以容纳新消息.

如上所述,一旦程序运行,缓存就开始发生,并且在程序运行足够长时间之后,大多数方法调用将通过缓存方法运行.正如它所说的那样,缓存在使用方法时发生,因此消息仅在使用时被缓存.

类方法本质上与C函数(或C++中的静态类方法)相同,还是有更多的东西?

类对象以类似于类实例的方式处理方法发送.每个类对象都有一个对象,它在一个名为a 的对象中存储自己的方法metaclass.类对象有自己的isa指向其元类对象的指针,该对象又具有超级元类对象,它可以从中继承类对象.对类方法的方法调度如下:

  1. 调度系统遵循类对象isa指向元类对象的指针
  2. 搜索元类对象的方法表以查找类方法.
  3. 如果未找到,则搜索继续到元类对象的超类,继续搜索.
  4. 重复此过程,直到找到该方法,或者直到它到达根元类,并抛出异常.

  • 优秀的解释.我发现理解动态调度系统以及如何使用选择器也很好地解释了为什么方法名必须是唯一的,而Objective-C不像Java和C++那样支持重载方法.因为选择器缓存为整数,所以具有相同名称的任何方法都将映射到同一选择器.无论好坏,它确实迫使程序员更多地考虑正确的命名方法. (7认同)
  • 如果你想深入... http://www.friday.com/bbum/2009/12/18/objc_msgsend-part-1-the-road-map/ (4认同)
  • 特殊的'前进'和'performv'方法怎么样?他们什么时候打电话? (2认同)

bbu*_*bum 19

我还在我的博客上的x86_64上通过objc_msgSend()的指令编写了一条指令,如果有人想深入探讨的话:

http://www.friday.com/bbum/2009/12/18/objc_msgsend-part-1-the-road-map/

  • 伟大的文章!+1 (3认同)
  • @DwayneRobinson有一个'fixup'阶段由dyld执行,作为一个可执行的负载,可以解析和规范所有这些常见数据.我不记得细节,坦率地说,自从我上次看以后,它们可能已经改变了.运行时和dyld的源代码可从http://www.opensource.apple.com获得.有趣的读物. (2认同)