c语言send方法,高效编写代码的方法(九):了解objc_msgSend

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

C中

一个C语言的例子:

#import

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

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函数,原型如下:

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 当发送消息是,动态绑定机制会帮助我们查找方法的实现并进行运行。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值