我所理解的Runtime:2、消息发送和消息转发

消息发送(Messaging)

8、以上便是runtime相关的一些数据结构,接下来我们回看一开始的疑问:
objc_msgSend()函数在执行的过程中是如何找到对应的类,找到对应的方法实现的呢?
这就是消息发送(messaging)的处理过程了:
(1)、对于上文的Class的数据结构的描述,官方文档只简略了把它们归纳成了两部分:一个指向其父类的指针和一个方法调用表(这个Class的所有方法的selector和实现代码所在地址的关联表);
(2)、当某个消息被发送到一个对象之后(即对象执行某个方法),runtime会根据这个对象的isa指针找到它所属的类,在类的方法调用表里查找对应的selector。如果没有找到的话,它会继续沿着类的super_class指针找到它的父类,在父类的方法调用表里查找对应的selector;
(3)、找到了对应的selector之后,就根据selector找到方法的实现代码的地址,执行这些实现的代码。如果没有找到,则会启用消息转发(message forwarding)机制,这个机制在后文会详谈;
(4)、所以一个方法的实现代码,并不是在编译的时候就确定好的,它是直到调用这个方法的时候,才通过消息发送机制,定位到方法的实现代码处执行,所以方法的调用和实现是动态绑定(dynamically bound)的;
(5)、当执行方法的实现代码的时候,objc_msgSend()函数不止会把实现代码需要的参数传给它,同时还会多传两个隐藏参数:self和_cmd。这两个参数其实就是objc_msgSend(receiver, selector)的receiver和selector,表面上objc_msgSend()函数只是把receiver和selector之后的那些参数传给了方法的实现代码(如果后面还有参数的话),实际上它偷偷地把receiver和selector也给传进去了,方法的实现代码里使用self和_cmd这两个形参就能调用到receiver和selector。
所以为什么当我们在编写一个方法的代码的时候,使用“self”就能直接调用到这个方法调用的对象,就是通过这个过程传递进来的;
(6)、为了提高消息发送的速度,每次在查找方法调用表前,会先查找一个类的cache(见前文7(7)),cache里存放了常用的方法的selector和实现代码地址的对应关系,如果在cache里能够找到对应的selector,那就可以直接跳到方法的实现代码处做执行,不需要再去跑剩下的消息发送流程。
判断方法是否“常用”依照了这样一个原则:如果一个方法被调用了一次,那么它就很有可能会被调用第二次,这个方法就会被加入cache。如果程序运行了足够久,让cache做了足够的热身(warn up),那么程序的运行会比一开始的时候更快,此时几乎所有需要调用的方法都能在cache里找到。
(7)、官方提供的消息发送的流程图如下:


动态方法解析(Dynamic Method Resolution)和消息转发(Message Forwarding)

9、那么还有一个疑问没有讨论,就是如果在消息发送的过程中发生了意外的话,它又会怎么处理呢?其实也就是8(3)中所提到的:如果消息发送没能找到对应的方法,那么runtime就会启用消息转发(message forwarding)机制来进行处理。
首先我们知道,正常情况下我们会在类的@implementation写好方法的实现代码,当执行这个方法的时候,runtime最终会绑定到这段实现代码并执行它,这是正常的流程。如果没有找到对应的实现代码,那么runtime会依次按照下面三个步骤来处理这个消息:
(1)、其实runtime并不会立刻就启动消息转发,首先runtime会做的是动态方法解析(Dynamic Method Resolution)。它调用当前类的类方法+resolveInstanceMethod:(处理实例方法)或+resolveClassMethod:(处理类方法),看看是否在方法中有动态添消息的方法实现,有则执行,无则继续下一步处理;
(2)、如果来到这一步,才是真正地开始消息转发了。runtime首先会进行快速转发(Fast Forwarding),它会调用当前类的- (id)forwardingTargetForSelector:方法,看看方法中是否有将此消息转发给其他类的处理,有则将消息转发给对应处理的类,无则继续下一步处理;
(3)、最后runtime会进行完整的消息转发(Normal Forwarding),它首先会调用- (NSMethodSignature *)methodSignatureForSelector:方法,如果方法能正常返回一个NSMethodSignature对象,那么它就会创建一个表示消息的NSInvocation对象,这个对象包含了消息相关的所有细节,然后调用- (void)forwardInvocation:方法进行完整的转发,如果- (void)forwardInvocation:方法中有对这个消息的相关转发处理,就将消息转发给对应的另一类进行处理处理,如果没有,则抛出unrecognized selector sent to instance或者unrecognized selector sent to class的异常信息。
这就是一个完整的消息转发处理流程。

10、我们可以通过@samlaudev的一个demo验证整个转发过程:
(1)、首先定义了一个Message类,并在类中定义了一个实例方法:



当调用了这个方法的时候:



会有如下输出:

这是一个正常的方法执行;

(2)、然后我们首先来验证第一步:动态方法解析。
将-(void)sendMessage:方法的实现代码注掉,同时添加以下方法:



这对应处理的第一步,此时-(void)sendMessage:方法已经没有正常的实现代码了,根据第一步,runtime会在+resolveInstanceMethod:方法中看看是否有动态添加-(void)sendMessage:方法实现,此时运行后输出:

说明runtime确实执行了动态方法解析;
(3)、然后我们来验证第二步,即是消息转发的第一步:快速转发给其他类处理。
此时需要新建一个其他类MessageForwarding,然后在MessageForwarding类中也定义一个-(void)message:方法:

然后回到Message类,把上一步的+resolveInstanceMethod:方法注掉,添加以下快速转发的方法:

意思即是将这个消息快速转发给MessageForwarding对象去处理,运行输出如下:

说明runtime执行了消息转发的第一步;
(4)、最后我们来验证处理的第三步,即是消息转发的第二步:将消息完整转发给其他类处理。
此时我们再新建一个类MessageNormalForwading,并在MessageNormalForwading类中也定义一个-(void)message:方法:



回到Message类,将第二步的-(id) forwardingTargetForSelector:方法注掉,然后添加以下两个方法:

将消息封装成一个NSIncocation对象,然后将它完整转发给MessageNormalForwading类去处理。执行后输出:

说明runtime完整地执行了消息转发的第二步。
由此我们验证了这三个步骤。

动态解析类方法和类型编码

11、在10(2)所处理的第一步中,如果需要动态解析的方法是类方法,应该怎么处理呢?
我们给Message类声明一个类方法+(void)classSendMessage:并且不做任何实现,然后需要在Message类中添加这样一个方法来处理:



执行以下代码后:



输出如下:

需要注意的地方是,在classSendMessage:方法内执行class_addMethod()函数时的第一个参数。

当我们添加实例方法的时候,class_addMethod()函数第一个参数传的是[self class],传当前的类;而添加类方法的时候,就需要传[self class]所属的类,当前类所属的类,即是元类(Meta Class)。
这正是我们在前文讨论过的,在类的method_list里添加方法,会成为它的实例可调用的方法,即是这个类的实例方法;在类所属的元类的method_list里添加方法,会成为元类的实例可调用的方法,元类的实例即是当前类,于是成为了这个类的类方法。

12、消息转发能让一个类通过把消息传递给其他类处理,来处理一些它本来不能处理的方法,看起来似乎能模拟“多重继承”的效果,通过把不同消息转发给其他类处理模拟了继承自其他类的效果。不过消息转发虽然类似于继承,但NSObject的一些方法还是能区分两者,如respondsToSelector:和isKindOfClass:只能用于继承体系,而不能用于转发链。

13、还有一个地方可以注意一下:在动态方法解析和完整消息转发中的相关方法中,都出现了这么一个字符串:"v@*",这个字符串是类型编码,它将消息中的方法归纳成几个字符串来表示。
比如上文消息转发的例子中,消息里的方法是-(void)message:,于是"v@*"中的v表示方法返回值为void,*表示方法的参数是NSString类型的,@则表示隐藏参数self。
隐藏参数在类型编码中是可写可不写的,所以考虑到还有另外一个隐藏参数_cmd,这个类型编码写成"v@:*"也是可以的。当然直接写成"v*"也没问题。

参考文档:
官方文档
https://github.com/samlaudev/RuntimeDemo
http://www.jianshu.com/p/25a319aee33d
http://www.cocoachina.com/ios/20141105/10134.html
http://www.cocoachina.com/ios/20141106/10150.html

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

推荐阅读更多精彩内容

  • 转至元数据结尾创建: 董潇伟,最新修改于: 十二月 23, 2016 转至元数据起始第一章:isa和Class一....
    40c0490e5268阅读 1,725评论 0 9
  • 这篇文章完全是基于南峰子老师博客的转载 这篇文章完全是基于南峰子老师博客的转载 这篇文章完全是基于南峰子老师博客的...
    西木阅读 30,567评论 33 466
  • 参考链接: http://www.cnblogs.com/ioshe/p/5489086.html 简介 Runt...
    乐乐的简书阅读 2,137评论 0 9
  • 我们常常会听说 Objective-C 是一门动态语言,那么这个「动态」表现在哪呢?我想最主要的表现就是 Obje...
    Ethan_Struggle阅读 2,199评论 0 7
  • 不喜欢就滚… 她转过身离开,试图遮掩她眼眶中的泪水。 看着她的离去背影我没有追上去,我知道,我那句话语已经伤透了她...
    ANYANA阅读 288评论 0 0