本章介绍如何将消息表达式转换为objc_msgSend函数调用,以及如何按名称引用方法。然后解释如何利用objc_msgSend
,以及如果需要,如何绕过动态绑定。
objc_msgSend 函数(重要!!!)
在Objective-C中,消息直到运行时才绑定到方法实现
。编译器转换消息表达式,
[receiver message]
调用消息传递函数 objc_msgSend
。此函数将接收方和消息中提到的方法的名称(即方法选择器)作为其两个主要参数:
objc_msgSend(receiver, selector)
消息中传递的任何参数也将传递给objc_msgSend:
objc_msgSend(receiver, selector, arg1, arg2, ...)
消息传递函数为动态绑定
:
- 先
绑定
查找选择器引用的过程(方法实现)。由于同一方法可以由不同的类实现,因此它找到的精确过程取决于接受者。 - 它然后
调用
过程,将接收对象(指向其数据的指针)以及为方法。 - 最后,它
传递
过程的返回值作为自己的回报值。
注意:编译器生成对消息传递函数的调用。永远不要在编写的代码中直接调用它。
消息传递的关键在于编译器为每个类和对象构建的结构
。每个类结构都包含以下两个基本元素
:
指向超类的指针
。一个类调度表
。此表中的条目将方法选择器(selector)与
其标识的方法
的类特定地址相关联
。setOrigin:
方法的选择器与setOrigin:
(实现的过程)的地址相关联,display
方法的选择器与display
的地址关联,依此类推。
创建新对象时,将为其分配内存,并初始化其实例变量
。对象变量中的第一个变量是指向其类结构的指针
。这个名为isa
的指针让对象访问其类,并通过该类访问它继承的所有类。
注意:虽然isa指针不是严格意义上的语言的一部分,但它是对象与Objective-C运行时系统一起工作所必需的。在结构定义的任何字段中,对象都需要与struct objc_object(在objc/objc.h中定义)“等效”。但是,您很少需要创建自己的根对象,并且从NSObject或NSProxy继承的对象自动具有isa变量
。
这些类元素和对象结构如图3-1所示。
Figure 3-1 Messaging Framework
Messaging Framework
当消息发送到对象
时,消息传递函数跟随对象的isa
指针指向类结构
,在该类结构中查找调度表中的方法选择器
。如果在那里找不到
选择器,objc_msgSend
会跟随指向超类的指针
并尝试在其调度表中查找选择器
。连续的失败导致objc_msgSend爬升类层次结构,直到到达NSObject类
。一旦找到选择器,函数就会调用表中输入的方法,并将接收对象的数据结构传递给它。
这就是在运行时选择方法实现的方式
,或者用面向对象编程的行话来说,方法是动态绑定到消息的
。
为了加快消息传递过程
,运行时系统在使用方法时缓存选择器和地址
。每个类都有一个单独的缓存
,它可以包含继承方法的选择器以及类中定义的方法的选择器。在搜索调度表之前,消息传递例程首先检查接收对象类的缓存
(理论上,曾经使用过一次的方法可能会再次使用)。如果方法选择器在缓存中,则消息传递只比函数调用稍慢
。一旦一个程序运行足够长的时间来“预热”它的缓存,它发送的几乎所有消息都会找到一个缓存方法。缓存动态增长以适应程序运行时的新消息。
使用隐藏参数
当 objc_msgSend找到实现方法的过程时,它调用该过程并将消息中的所有参数传递给它。它还向过程传递两个隐藏参数
:
接收对象
方法的选择器
这些参数为每个方法实现提供关于调用它的消息表达式的两部分的显式信息。它们被称为“隐藏”,因为它们没有在定义方法的源代码中声明
。它们在代码编译时被插入到实现中。
虽然这些参数没有显式声明,但是源代码仍然可以引用它们(就像它可以引用接收对象的实例变量一样)。方法将接收对象
引用为self
,并将其自己的选择器
引用为_cmd
。在下面的示例中,_cmd
表示异常方法的选择器,self
指向接收到异常消息的对象。
- strange { //_cmd:表示异常的选择器 //self:指向接收到异常消息的对象 id target = getTheReceiver(); SEL method = getTheMethod(); if ( target == self || method == _cmd ) return nil; return [target performSelector:method]; }
在这两个论点中,self更有用。实际上,这是接收对象的实例变量可用于方法定义的方式
获取方法地址
规避动态绑定的唯一方法是获取方法的地址,然后像函数一样直接调用它
。当一个特定的方法将被连续执行很多次,并且您希望避免每次执行该方法时消息传递的开销,这种情况可能比较合适。
使用NSObject
类中定义的方法methodForSelector:
,可以请求指向实现方法的过程的指针,然后使用该指针调用该过程
。methodForSelector:
返回的指针必须谨慎
地转换
为正确的函数类型
。类型转换中应包括返回类型和参数类型。
下面的示例显示如何调用实现setFilled:方法的过程:
void (*setter)(id, SEL, BOOL); int i; setter = (void (*)(id, SEL, BOOL))[target methodForSelector:@selector(setFilled:)]; for ( i = 0 ; i < 1000 ; i++ ) setter(targetList[i], @selector(setFilled:), YES);
传递给过程的前两个参数是接收对象(self)
和方法选择器(_cmd)
。这些参数隐藏在方法语法中,但必须在方法作为函数调用时显式。
使用methodForSelector:
绕过动态绑定可以节省消息传递所需的大部分时间。但是,只有在特定的消息被重复多次时,节省的空间才会显著,如上面所示的for循环中。
注意methodForSelector:
是由Cocoa运行时系统提供的
;它不是Objective-C语言本身的特性
。