Objective c 中的消息传递机制

本文详细介绍了Objective-C中的消息传递机制,包括objc_msgSend的工作原理及其动态绑定过程。此外,还探讨了类和消息之间的松耦合关系以及如何在运行时创建方法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Objective-C中调用函数的方法是“消息传递”,这个和普通的函数调用的区别是,你可以随时对一个对象传递任何消息,而不需要在编译的时候声明这些方法。所以Objective-C可以在runtime的时候传递人和消息。

在C++或Java中调用某个类的方法,在Objective-C中是给该类发送一个消息。在C++或Java里,类与类的行为方法之间的关系非常紧密,一个方法必定属于一个类,且于编译时就已经绑定在一起,所以你不可能调用一个类里没有的方法。而在Objective-C中就比较简单了,类和消息之间是松耦合的,方法调用只是向某个类发送一个消息,该类可以在运行时再确定怎么处理接受到的消息。也就是说,一个类不保证一定会响应接收到的消息,如果收到了一个无法处理的消息,那么程序既不会出错也不或宕掉,它仅仅是什么都不做,并返回一个nil【在编译期是不出错的,符合语义上的理解,但是runtime运行时的话,会崩溃】。这种设计本身也比较符合软件的隐喻。

很显然,既然编译期并不能确定方法的地址,那么运行期就需要自行定位了。而Objective-C runtime就是通过“id objc_msgSend(id theReceiver, SEL theSelector, ...)”这个函数来调用方法的。其中theReceiver是调用对象,theSelector则是消息名,省略号就是C语言的不定参数了。

objc_msgSend的动态绑定过程
1.根据receiver对象去查找selector方法的具体实现位置
2.调用查找到的实现,传递参数
3.将方法实现的返回值作为自己的返回值,返回
那objc_msgSend的是如何查找方法的具体实现位置呢,从网上找了一下,如下:
编译器构建每个类的时候,每个类必须包含二个必要的元素:
•指向父类的指针
•一个调度表(dispatch table),调度表将类的selector与方法的实际内存地址关联起来。
我们知道每个对象都有一个isa指针,指向所属类,通过这个isa指针可以找到对象的所属类和所属的父类...
查找过程如下:



(图片来源网络)


当想一个对象发送消息的时候,先根据isa找到所属的类,然后去查找该类的dispatch table,如果没有找到,就去其父类中查找...如果找到了,就根据调度表中的内存地址调用该实现,如果最后一直没有找到返回nil。

-----------------------------------------------------

如前所述,ObjC是以消息机制来工作的,但其实诸如-(void)foo:(int)a的语句在编译时被objc_msgSend(receiver,selector,arg1,arg2,….)替换了,所以其实每一条发送消息的代码本质上还是调用函数(call function),不过他们调用的都是同一个函数objc_msgSend(也可能是objc_msgSend_stret(返回值是结构体),objc_msgSend_fpret(返回值是浮点型)等)
  分析objc_msgSend的参数,第一个receiver的类型是id,代表接受消息的对象,第二个是selector代表接收对象的方法,后面的是该方法的参数,之前那条语句的被编译器替换后就是:

[theClass foo:10]  -> objc_msg(theClass,@selector(foo:),10);

因为消息的接受对象和接受对象的方法都参数化,所以在运行时刻,接受对象和接受对象的方法都可以是动态的!

比如说程序里面可以这样写:

id helper = getTheReceiver();  
SEL request = getTheSelector();  
[helper performSelector:request]; 

  它的实现是基于ObjC runtime. NSObject类实现了这套机制,所以每一个继承于NSObject的类都能自动获得runtime的支持 。在这样的一个类中,有一个isa指针,指向该类定义的数据结构体,这个结构体是由编译器编译时为类(须继承于NSObject)创建的.在这个结构体中有包括了指向其父类类定义的指针以及Dispatch table. Dispatch table是一张SEL和IMP的对应表 。  对于名称相同的方法,他们都有相同的SEL,方法的名称不包括类名称,所以子类和父类中的同名方法拥有相同的SEL,但是他们的实现可以各不相同,因而在他们各自的Dispatch表中SEL所对应的IMP是不同的,IMP是一个函数指针,而虽然每一个SEL对应的是一个方法的名称,但考虑到效率,SEL本身是一个整型,编译器会另外生成一张SEL和方法名称对应的表 。有了这样的结构,objc就可以实现多态了 。还是这行代码:

[theClass foo:10];

是向theClass发送了foo:消息,那么首先在theClass的类结构的Dispatch table里找有没有对应的SEL,如果有的话,就表示theClass有响应该消息的方法,程序就跳到该方法的代码地址头(由IMP指定),开始执行 。如果在theClass的Dispatch table找不到对应的SEL,那么就会通过isa所指的结构体中包含的父类指针,到父类里面去寻找,如果到最后还是没有找到,就会出现runtime error.所以说,即使theClass以及它的父类都没有定义-(void) foo:(int)a方法,程序还是可以通过编译,但如果是用xcode的话,编译器会有警告,告知theClass可能无法响应该消息 。不会报错的原因是类的方法也可以在执行时刻创建!上面的代码:

class_addMethod([MyClass class], @selector(dynGeneratedMethod:),(IMP)myClassIMP,”v@:i”); 

  就是给MyClass类在执行时刻增加了一个响应dynGeneratedMethod:消息的方法,这样之后对任何MyClass的instance类发送dynGeneratedMethod:消息,就会得到响应了.myClassIMP是类收到该消息时要调用的方法,其声明如下:

void myClassIMP(id _rec, SEL _cmd, int theInt) 

  这个方法的前面两个参数是必须的,之后的参数才是我们实际用到的参数,数目和@selector()中的冒号数一样,冒号数代表的就是参数个数  。第一个参数是消息的接受对象,是MyClass的实例,第二个参数是由SEL代表的具体消息  。

  Class_addMethod的最后一个参数是表示dynGeneratedMethod:的返回值和参数信息,不过我自己试了一下,这个参数不起作用  。

  几个要点:

  1、对于C中被称为函数(function)和函数调用(function call)的地方,在ObjC中被叫做方法(method)和发送消息(send message).试图调用未定义的方法会导致编译错误,而发送一条消息,即使没有任何类定义了响应该消息的方法,编译时也不会报错,从语义上讲这也是对的,发一条消息本来就不要求一定有人会响应,不过如果执行到发送消息的代码时真的没有类可以响应的话,是会发生runtime error,为了避免这种事情发生,可以先进行检测,这样写:

if( [myClass respondsToSelector:@selector(foo:)])  
{  
   [myClass foo:10];  
} 

  我感觉ObjC这样的一套sender receiver的定义更注重面向对象的概念。类是一个接收者(receiver),如果定义了某个方法,就可以接收和这个方法名称相同的消息。而使用该类的client(sender),则尝试向该类发送消息.如果匹配了,就跳到类的方法里执行。

  2、方法名称是诸如foo:,不包括返回类型,参数类型,而又因为一个foo:对应于一个SEL,所以说ObjC不支持相同的foo:有不同的返回类型,也不支持重载 。不过类方法和实例方法可以有相同的名字,而又有不同类型的参数和返回类型,因为它们不是处在同一张dispatch table中。

        3、不仅类的方法可以运行时刻创建,类本身也可以在运行时刻创建,前面提到继承于NSObject的类,编译器会帮忙生成ObjC runtime所需要的类结构定义,只要我们在代码里也按照那个结构创建了自己的类,那一样可以获得ObjC runtime的支持。


引用网址:http://yangxu850611.blog.163.com/blog/static/174789113201110222115417/

http://www.cnblogs.com/ItNoob/archive/2012/11/21/objc_msg_send.html

另附进阶文章一篇。

http://www.cnblogs.com/ygm900/archive/2013/01/16/2862676.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值