这一节讲述 消息(message) 的表达 如何转换成 objc_msgSend 函数调用,以及如何通过方法名找到方法。然后会讲解如何利用 objc_msgSend 函数,以及如何运用动态绑定
The objc_msgSend Function(messaging function)
Objective-C 中,消息(messages)直到 运行时(runtime) 才会和 方法实现(method implementations) 绑定。例如
[receiver message]
编译器会将这个 消息表达式 转换成 objc_msgSend函数。objc_msgSend函数 需要两个最主要的参数,消息接收者(receiver)和方法(method selector),则上面这个方法调用会被转换成如下格式
objc_msgSend(receiver, selector)
如果方法接收多个参数,则参数也会传入objc_msgSend函数,如下
objc_msgSend(receiver, selector, arg1, arg2, ...)
The messaging function (即 objc_msgSend函数)为 动态绑定(dynamic binding)做了充分的准备:
@1 首先 objc_msgSend函数 找到selector对应的 方法实现(method implementation)。
@2 接着 objc_msgSend函数 调用方法实现
@3 最后 objc_msgSend函数 将方法实现的返回值作为它自己的返回值
注:编译器会生成消息调用函数objc_msgSend,你不应当直接进行调用
编译器会为每个类和对象创建结构体,每个类的结构体都含有两个必要的元素:指向父类的指针 和 类的分发表(dispatch table)
分发表将方法(method selectors)与方法的地址相关联。
当一个新的对象被创建,在这个对象的变量中首要的是一个指向类结构的指针,我们叫做isa指针,该指针使对象可以找到它所属的类,以及继承链中所有的类,如下图
Messaging Framework
当一个消息被发送给一个对象,消息函数(messaging function,即objc_msgSend函数)根据对象的isa指针找到类结构,然后在类结构的分发表(dispatch table)中查询消息的方法(method selector).如果无法找到该方法,objc_msgSend函数会找到 指向父类的指针 ,然后在父类的分发表中查询此方法。连续的查找失败会使objc_msgSend沿着继承链一直查找到NSObject类。一旦方法(selector)被找到,objc_msgSend函数会调用它并将对象的数据结构传递进去。
方法实现(method implementations)就是以这种方式在运行时(runtime)被选择出来,方法(methods)和消息(messages)动态绑定在一起
为了加速消息调用(messaging)的过程,运行时系统(runtime system)会将已经使用过的方法选择器(selectors)和方法的地址缓存起来。每个类的缓存是分开的,并且可以同时缓存继承来的选择器(selectors)和自定义的选择器。在搜索分发表之前,消息调用程序(messaging routine)会首先检查消息接收对象的类的缓存。如果方法选择器(method selector)存在于缓存中,消息传递的速度就只比函数调用慢一丁点(这句话的意思就是说很快)。一旦一个程序被运行足够长的时间,以至于几乎所有的消息传递都能找到对应的缓存。缓存会动态的增加以保证可以容纳新消息。
Using Hidden Arguments
当函数objc_msgSend 找到了方法的实现,它调用这个实现并且传递给它消息传递的所有参数。同时也传递给它两个隐藏参数:接收对象和方法选择器
这些参数给方法实现提供了明确的信息。一个方法用self指代接收对象,用_cmd指代选择器。例如
- strange
{
id target = getTheReceiver();
SEL method = getTheMethod();
if ( target == self || method == _cmd )
return nil;
return [target performSelector:method];
}
在上面的例子中,_cmd代表strange方法的选择器,self指代收到strange消息的对象。self参数十分重要,方法能调用对象的实例变量都通过self参数实现
Getting a Method Address
越过动态绑定的唯一方法就是获取方法的地址,并且直接以函数方式进行调用。这种情况很少用到,不过,如果一个方法被调用很多次,而你想省去多次调用过度的消息传递(messaging),这种情况下,就会派上用场
通过NSObject类定义的方法,methodForSelector:,你可以请求到一个指向方法实现的指针,然后通过这个指针调用方法实现。methodForSelector:方法返回的指针必须用合适的函数类型接收。返回值和参数都需要包含在相应的表达式中。例如:
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);
在上面的例子中展示了setFilled:方法可能的一种调用方式
传递给方法实现的两个首要参数:接收对象(self),方法选择器(_cmd),这些参数隐藏在方法体中,但是如果使用函数方式调用就需要显式的传递。
使用methodForSelector:跳过动态绑定省去了消息传递(messaging)的时间,然而,只有在一个方法显著的重复调用多次时,这种方式的优势才能体现,比如上面例子中的for循环调用
注意:methodForSelector:是Cocoa 运行时系统(runtime system)提供的;而不是Objective-C语言的特性