消息
这章节讲述怎么样把消息表达式转换成objc_msgSend方法调用,并且怎么样通过名称引用方法。然后解释怎么利用objc_msgSend,并且如果必要的话,怎么避免动态绑定。
objc_msgSend方法
在Objective-C中,消息不必去方法实现直到运行时。编译器把一个消息表达式转换成消息方法objc_msgSend上的一次调用。这个方法需要接收者和在消息中提到方法的名称——即方法选择器——作为它的两个最重要的参数:
[receiver message]——>objc_msgSend(receiver, selector)
任何传入消息的参数也都交给objc_msgSend:
objc_msgSend(receiver, selector, arg1, arg2, ...)
发送消息方法做所有动态绑定必须的事情:
1. 它先找到选择器引用的程序(方法实现)。既然相同的方法可以被不同的类不同地实现,它找到精确的程序依靠接受者的类。
2. 它然后调用程序,把接受对象(指向它的数据的指针)和为方法指定的任何参数传给它。
3. 最后,它传递程序的返回值作为自己的返回值。
备注:
编译器为发送消息方法产生调用,你不应该在你写的代码中直接调用它。
发送消息的关键在于编译器为每一个类和对象创建的结构。每一个类结构包含这两个基本的元素:
1. 指向父类的指针。
2. 类调度表。表中有方法选择器和它们确定的方法的指定类的地址相互关联的记录。setOrigin:方法的选择器是和setOrigin:的地址(实现的程序)相互关联的,delay方法的选择器是和delay的地址相互关联的等等。
当一个新对象被创建的时候,为它分配内存,并且初始化它的实例变量。在对象变量中的第一个变量是一个指向他的类结构的指针。这个指针,被称作isa,让对象能够访问它的类并且通过这个类,能访问它继承的所有类。
备注:
尽管不是严格地语言的一部分,isa指针被要求作为一个和Objective-C运行时系统一起工作的对象。无论结构定义在什么地方,这个对象都需要和结构体objc_object(定义在objc/objc.h中)是相同的。然而,你很少,如果曾经有的话,需要创建你自己的根对象,这个对象继承自自动带有isa变量的NSObject或NSProxy。
这些元素的类和对象结构如图3-1所示。
当向对象发送消息的时候,发送消息方法跟随对象的isa指针到达类结构从这开始它在调度表里向上查找方法选择器。如果它不能在那找到选择器,objc_msgSend
跟随指针到达父类并且尝试在它的父类调度表中找到选择器。接连的失败导致objc_msgSend沿着类的层级一直向上直到NSObject类。一旦定位到选择器,objc_msgSend就会调用表里输入的方法并且把接收者的数据结构传给它。
这就是方法实现在运行时被选择的方式——或者,用面向对象编程的行话说,就是方法动态绑定消息。
为了加速消息处理,当选择器和方法的地址被使用的时候运行时系统就会缓存它们。每个类都有一个单独的缓存,并且它既包含继承的方法的选择器又包含在类中定义的方法的选择器。在搜索调度表之前,消息程序首先检查接收者对象的类的缓存(理论上说若果一个方法被使用了一次那么它很可能再次被使用)。如果方法选择器在缓存中,发消息只比方法调用稍慢点。一旦一个程序运行了总够长的时间来使它的缓存变得活跃,几乎它发送的所有消息都可以找到一个缓存方法。当程序运行的时候,缓存的动态增加以适应新消息。
使用隐藏的参数
当objc_msgSend找到实现了一个方法的程序的时候,它调用这个程序并且把消息里的所有参数传给它。它也会把两个隐藏的参数穿个这个程序:
1. 接收者对象
2. 方法选择器
这些参数给每一个方法实现明确的关于两个一半的触发它的消息表达式的信息。这些参数被成为隐藏的,因为他们没有在定义方法的源代码中被声明。当代码被编译的时候,他们就被插入到实现中。
尽管这些参数没有被明确地声明,源代码仍然可以引用它们(就像它可以引用接收者对象的实例变量那样)。方法把接受对象称作self,和把它自己的选择器称作_cmd。在下面的例子中,_cmd指的是strange方法的选择器和self指的是接受strange消息的对象。
- strange {
id target = getTheReceiver();
SEL method = getTheMethod();
if ( target == self ||method == _cmd )
return nil;
return [targetperformSelector: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语言本身的一个特性。