消息机制

笔者翻译自iOS Developer Library Messaging

消息机制

这章讲解了消息表达式如何被转化成 objc_msgSend 函数调用和怎样通过函数名关联方法。然后还解释了你如何利用 objc_msgSend,并且在你需要的时候如何绕过动态绑定。

objc_msgSend 函数

在 Object-C 中,消息是直到运行时才绑定相应的方法实现。编译器将转换消息表达式,

<pre><code>[receiver message]
</code></pre>

为一个消息函数调用,objc_msgSend。该函数将接收者(receiver)和在消息中提到的函数名(也叫函数选择器)作为它的两个主要参数:

<pre><code>objc_msgSend(receiver, selector)
</code></pre>

消息传递的任何其他参数也将传递给 objc_msgSend:

<pre><code>objc_msgSend(receiver, selector, arg1, arg2, ...)
</code></pre>

消息函数将为动态绑定完成所有必须的事情:

  • 它首先找到函数选择器指定的程序块(函数方法的实现)。因为相同的方法在不同的类中有不同的实现,它找到的正确的程序块将依赖接收者对应的类。
  • 然后它将消息接收者对象(一个指向它的数据的指针)和该方法相关的其他参数传给该代码块,并调用该代码块。
  • 最后它将该代码块的返回值作为它自己的返回值。

提示:编译器将生成消息函数的调用。你不应该直接在你所写的代码中调用它。

消息机制的关键在于编译器为每个类和对象建立的结构。每个类结构都包含这两个必要的元素:

  • 指向父类的指针。
  • 类分配表。这个表有一些数据项,他们将方法选择器和该类定义的方法的地址相对应。方法 setOrigin:: 的选择器和 setOrigin:: 的地址(方法的实现)相对应,方法 display 的选择器和 display 的地址相对应,等等。

当一个对象被创建,它的内存被分配,并且它的实例变量被初始化。在对象中的第一个变量就是指向它的类结构的指针。这个指针叫做 isa,它使对象能访问它所属的类,并且通过这个类能访问所有它继承的类。

提示:尽管 isa 指针严格上不是语言的一部分,但它是一个对象能在 Object-C 运行时系统中工作所必须的。一个对象需要在 struct objc_object(定义在 objc/objc.h中)定义的任何字段域上保持对等。然而,你很少需要去创建自己的根类,而继承自 NSObject 和 NSProxy 的对象将自动带有 isa 变量。

这些类和对象的结构元素如图所示。

Messaging Framework.jpg

当消息被发送到一个对象,消息函数将沿着对象的 isa 指针到类结构,并在分配表(dispatch table)中查找方法选择器。如果它不能在这里找到方法选择器,obj_msgSend 将沿着父类指针并尝试在父类的分配表中找到方法选择器。连续的失败使 obj_msgSend 沿着类的继承关系向上走,直到到达 NSObject 类。一旦它找到了选择器,消息函数将调用分配表中的方法并把接收者对象的数据结构传递给它。

这就是方法实现在运行时被选择的方式——或者,按面向对象编程的行话来说就是,方法被动态绑定给消息。

为了加速消息处理,当方法被调用时,运行时系统缓存了选择器和方法地址。这是一个相对于每个类分开的缓存,它能够包含继承的方法和自身定义的方法的选择器。在搜索分配表之前,消息程序首先检查接收者对象所在类的缓存(基于这样的理论:一个方法一旦被使用,很可能会被再次使用)。如果这个方法选择器在缓存中,消息机制只会稍微比直接函数调用慢一点。一旦程序已经运行了足够长的时间去“热身”它的缓存,几乎它发送的所有消息都能找到一个缓冲的方法。当程序运行时,缓存将动态地成长去适应新消息。

使用隐藏参数

obj_msgSend 找到实现方法的程序块,它调用该程序块并把消息中的所有参数传递给代码块。它也传递两个隐藏的参数:

  • 接收者对象
  • 方法选择器

这些参数把调用它的消息表达式的两部分直接信息传给了每个方法实现。它们被认为是隐藏的,因为它们没有在定义方法的源代码中声明,当代码被编译时,它们被插入到了函数的实现。

尽管这些参数没有被直接声明,源代码仍然能直接引用它们(就像它能直接引用接收者对象的实例变量一样)。一个方法把接收者对象当做 self,把自身的选择器当做 _cmd。在下面的例子中,_cmd 是指 strange 方法的选择器,self 是指接收 strange 消息的对象。

- strange
{
    id  target = getTheReceiver();
    SEL method = getTheMethod();

    if ( target == self || method == _cmd )
        return nil;
    return [target performSelector:method];
}

self 在这两个参数中更加有用。实际上,它使得消息的接收者对象的实例变量在方法定义中可以被访问。

得到方法地址

绕过动态绑定的唯一方法就是得到一个方法的地址并且把它当做一个函数直接调用它。在极少的情况下,当一个指定的方法将被连续执行很多次并且你想避免每次方法执行时消息机制带来的额外开销时,绕过动态绑定就变得合理了。

使用在 NSObject 类中定义的方法 methodForSeletor:,你能得到一个指向方法实现的程序块的指针,并能使用这个指针去调用该程序块。methodForSeletor:返回的指针必须转换成合适的函数类型。返回值和参数类型都应该包含在转换中。

下面的例子展示了实现了 setFill: 方法的代码块如何被调用:

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 运行时系统提供的,它并不是一个 Object-C 语言自身的特性。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 218,451评论 6 506
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,172评论 3 394
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,782评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,709评论 1 294
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,733评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,578评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,320评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,241评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,686评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,878评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,992评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,715评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,336评论 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,912评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,040评论 1 270
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,173评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,947评论 2 355

推荐阅读更多精彩内容