高效编写代码的方法(九):了解objc_msgSend

在OC中我们调用方法也叫作给对象发消息,消息包含了名字,选择器,参数及返回值等信息。

C中

一个C语言的例子:

#import <stdio.h> 
 
void printHello() {  
    printf("Hello, world!\n");  
}  
void printGoodbye() {  
    printf("Goodbye, world!\n");  
}  
 
void doTheThing(int type) {  
    if (type == 0) {  
        printHello();  
    } else {  
        printGoodbye();  
    }  
    return 0;  
} 

在C语言中,在不考虑使用内联函数的情况下,printHello和printGoodbye函数都是已知的。在调用时,编译器直接发出指令去进行调用,函数的地址通过硬解码得到。

现在换一种写法:

#import <stdio.h> 
 
void printHello() {  
    printf("Hello, world!\n");  
}  
void printGoodbye() {  
    printf("Goodbye, world!\n");  
}  
 
void doTheThing(int type) {  
    void (*fnc)();  
    if (type == 0) {  
        fnc = printHello;  
    } else {  
        fnc = printGoodbye;  
    }  
    fnc();  
    return 0;  
} 

以上代码则使用了动态绑定的方法,直到运行的时候,fnc函数具体是什么函数是未知的。与第一段代码不同的是,这里获得函数地址的方法不能硬解码获得,而是在运行期间得到。

OC中

OC中,对象调用方法,也叫给对象发送消息,实际上是使用了动态绑定机制。在底层,所有方法都是普通的C语言函数,然而对象收到消息之后,究竟该调用哪个方法则完全于运行期决定,甚至可以在程序运行时改变,这些特性使得Objective-C成为一门真正的动态语言。
通常我们在OC中这样发送消息:

id returnValue = [someObject messageName:parameter];

someObject是消息的接受者,messageName是一个选择器,parameter则为参数。选择器+参数 就是我们所称为的消息。
在底层,编译器将我们的消息转换文标准的C函数形式,如下:

void objc_msgSend(id self,SEL cmd,…)

self 为消息接收者,cmd为选择器,省略号为参数,表示可变长度参数。
因此,以上的消息转换为标准的C函数后如下:

id returnValue = objc_msgSend(someObject,@selector(messageName),paramter)

之所以objc_msgSend方法总能找到正确的函数去执行,原因如下:
其实每个类中都有一张方法列表去存储这个类中有的方法,当发出objc_msgSend
方法时候,就会顺着列表去找这个方法是否存在,如果不存在,则向该类的父类继续查找,直到找到位置。如果始终没有找到方法,那么就会进入到消息转发机制(后续知识,以后章节会介绍) 。
OC runtime还有一个机制在于方法缓存,每调用完这个方法后,一个方法映射就会被缓存起来,如果之后调用相同的方法,那么就能直接从映射表里确定方法的位置,而不用每次都需要查找,这样执行速度会快一点。

几个特殊方法

objc_msgSend_stret
如果待发送的消息要返回结构体,那么可交由此函数处理。只有当CPU的寄存器能够容纳得下消息返回类型时,这个函数才能处理此消息。若是返回值无法容纳于CPU寄存器中(比如说返回的结构体太大了),那么就由另一个函数执行派发。此时,那个函数会通过分配在栈上的某个变量来处理消息所返回的结构体。

objc_msgSend_fpret
如果消息返回的是浮点数,那么可交由此函数处理。在某些架构的CPU中调用函数时,需要对“浮点数寄存器”(floating-point register)做特殊处理,也就是说,通常所用的objc_msgSend在这种情况下并不合适。这个函数是为了处理x86等架构CPU中某些令人稍觉惊讶的奇怪状况。

objc_msgSendSuper
如果要给超类发消息,例如[super message:parameter],那么就交由此函数处理。也有另外两个与objc_msgSend_stret和objc_msgSend_fpret等效的函数,用于处理发给super的相应消息。

以上内容摘抄自网上翻译,因为英文原文这部分实在是不太好理解。
我觉得可以简单的按照字面意思来进行选择,比如你希望函数返回体为结构体,那么就使用objc_msgSend_stret,否则有几率会崩溃。返回值为浮点数时也是相同道理。

上文说过,当找到相应的方法时,会跳转过去。之所以可以这样实现,是因为每一个Objective-C函数都可以看作是一个简单的C函数,原型如下:

<return_type> Class_selector(id self,SEL _cmd,...)

以上Class及selector的命名是为了方便理解。每个类中都有一张类似于字典的方法表,而selector就相当于查找方法的key,objc_msgSend函数就是通过查这张表来实现跳转的。之所以以上原型和objc_msgSend方法长的非常相像,是为了更好使用tail-call技术来时方法的跳转更加优化。

如果某函数的最后一项操作是调用另外一个函数,那么就可以运用“tail-call”技术。
此时编译器会生成调转至另一函数所需的指令码,而不会向调用堆栈中推入新的“栈帧”。tail-call使用的条件比较苛刻,除了要求函数的最后一项操作是调用另外一个函数外,,并且要求另外一个函数不是有返回值的函数类型。tail-call对objc_msgSend非常关键,如果不这么做的话,那么每次调用Objective-C方法之前,都需要为调用objc_msgSend函数准备“栈帧”,若是不优化,还会过早地发生“栈溢出”(stack overflow)现象。

在写OC中,我们其实并不需要了解那么多底层的东西,但是我们需要知道调用一个方法之后,OC底层都发生了什么。

总结

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

推荐阅读更多精彩内容

  • 前言 runtime其实在我们日常开发过程中很少使用到,尤其是像我现在比较初级的程序猿就更用不到了。但是去面试很多...
    WolfTin阅读 595评论 0 2
  • 转至元数据结尾创建: 董潇伟,最新修改于: 十二月 23, 2016 转至元数据起始第一章:isa和Class一....
    40c0490e5268阅读 1,670评论 0 9
  • 本文详细整理了 Cocoa 的 Runtime 系统的知识,它使得 Objective-C 如虎添翼,具备了灵活的...
    lylaut阅读 788评论 0 4
  • 兜兜转转兜兜,我又回到了热电。大学学热能,当时迷茫的很,没有好好学功课,研究生转专业到光电,觉得自己大概不用再搞热...
    于渌阅读 363评论 0 0
  • JS 如何清除页面缓存 1.动态页面:index.asp?id=.... 2.使用jquery,$.ajaxSet...
    浮生小孟阅读 293评论 0 2