OC运行时消息

来源:互联网 发布:一叶知秋落叶知冬 编辑:程序博客网 时间:2024/05/17 23:17

消息发送
本章描述内容:消息表达式是如何转化成objc_msgsend函数调用,和 如何通过明知调用方法。然后说明如何利用objc_msgsend函数,并且,有必要时,如何绕开动态绑定。

objc_msgsend函数
在OC中,程序运行之前,消息是不会绑定到方法实现的。编译器会将一个消息表达式转化

[receiver message];

为对函数objc_msgsend的调用。这个函数携带消息——也就是说,selector方法——中提到的接收者和方法名字,作为它两个主要的参数:

objc_msgsend(receiver, selector);

消息中所有的参数也提交给objc_msgsend函数:

objc_msgsend(receiver, selector, arg1, arg2, ...);

消息传递函数为动态绑定做了所有需要的事情:
1. 首先它要找到selector涉及的程序(方法实现),由于在不同类中,同一方法的实现不同,所以它需要依靠接收者类来查找到精确的程序;
2. 然后它调用这个程序,并传递它接收到的对象(指向对象的指针),连同方法中指定的所有参数;
3. 最后,它将程序的返回值作为它自己的返回值。
注:编译器生成对消息传递函数的请求,你不能直接在你的代码中调用。
消息传递的关键在于编译器为每个类和对象创建的结构体,每个类的结构体包含两个基本的元素:
1. 一个指向父类的指针。
2. 一个类调度表。这个表有很多条目,关联方法选择器与它们识别的类的地址。setOrigin::方法的选择器与setOrigin::的地址(执行程序)关联在一起,display方法的选择器与display的地址关联,等等。
当一个对象被创建时,它的内存已被分配,并且它的实例变量已被初始化。在该对象的变量中,首位是一个指向类结构体的指针。这个指针,被称为isa,提供对象使用类,并通过该类,可以使用它所继承的类。
注:isa指针从严格意义上来说不是语言的一部分,它需要一个对象来完成OC运行时系统中的工作。一个对象需要”等效“于一个符合在任何结构体定义的领域的 struct objc_object(定义与objc/objc.h)然而,你很少需要去创建自己的根对象,并且,继承与NSObject和NSProxy的对象自动都有isa变量。
这些类和对象结构体的基本元素如插图3-1所示:
图3-1 Messaging FrameWork
当一个消息发送给对象时,消息传递函数跟随对象的isa指针到能在调度表中查找方法选择器的类结构体。如果在没有找到选择器,那么objc_msgsend沿着父类的指针去尝试寻找调度表中的选择器。连续的失败导致objc_msgsend沿着类等级往上一级攀爬,直到NSObject类。一旦定位到选择器,函数会调用该方法进入调度表,并将它传递给接收对象的数据结构体。
这是运行时选择方法实现的方式-或者,在对象的术语中-面向对象编程,方法和消息是动态绑定的。
考虑消息到传递进程的速率,运行时系统会贮存已经用到过的选择器和方法地址。每个类都有一个单独的贮存空间,并且它还包含了继承的方法和该类自定义个的方法相应的选择器。在检索调度列表之前,消息传递机制通常会先检查接收对象的类的缓存(理论上来讲,一个方法用了一次有可能会用第二次)。如果方法选择器在该类的缓存中,消息传递仅比函数调用慢一点。如果程序运行的时间足够长来“预热”缓存,基本上所有它发送的消息都能找到一个缓存的方法。缓存在程序运行中会动态的增长以容纳新的消息进入。

使用隐含参数
当objc_msgSend找到方法实现的程序时,它调用该程序并将所有参数传递给消息。它也传递给程序两个隐含的参数:
1. 接收对象
2. 方法的选择器
这两个参数为每个方法的实现提供了关于它调用的方法表达式的详细信息。它们之所以被称为“隐含的”,是因为它们并没有在方法定义的源码中声明。它们在代码被编译时才插入到方法实现中的。
尽管这些参数没有明确的声明,源码仍然能够追寻到它们(正如它能追寻到接收对象的实例变量)。一个方法提及到接收对象如self,涉及自己的选择器如_cmd。在下面的例子中,_cmd查阅陌生方法中的选择器,self查阅接收一个陌生消息的对象。

- strange{    id target = getTheReceiver();    SEL method = getTheMethod();    if (target == self || method == _cmd) {        return nil;    }    return [target performSelector:method];}

self在两个参数中更有用。实际上,它是使接收对象的实例变量在方法定义中可用的方式。

获取方法地址
绕过动态绑定的唯一方法是,获取方法的地址并直接调用它假定它是一个函数。这可能适用于少部分情况下,当一个特定的方法将被一连串运作多次并且你想避免方法每次运作消息传递的开销。
通过NSObject类中定义的一个方法:methodForSelector,你能申请一个用来实现方法的程序指针,然后用这个指针去调用该程序。methodForSelector返回的指针必须小心的抛出合适的函数类型。返回类型和参数类型都应该包含在抛出中。下面的例子展示了setFailed:方法的实现程序是如何被调用的。

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循环展示的那样。

0 0
原创粉丝点击