格而知之7:我所理解的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
www.jianshu.com/p/25a319aee33d
http://www.cocoachina.com/ios/20141105/10134.html
http://www.cocoachina.com/ios/20141106/10150.html
格而知之7:我所理解的Runtime(2)的更多相关文章
- 格而知之3:Core Data的基本使用
最近准备做一个随手笔记类的app给自己用,考虑到从未使用过Core Data,就决定用Core Data来做数据存储.在网上参考了一些Core Data的资料后,用一天的时间写了这个demo,主要测试 ...
- 理解 Objective-C Runtime
初学 Objective-C(以下简称ObjC) 的人很容易忽略一个 ObjC 特性 —— ObjC Runtime.这是因为这门语言很容易上手,几个小时就能学会怎么使用,所以程序员们往往会把时间都花 ...
- 理解Objective-C Runtime(四)Method Swizzling
Objective-C对象收到消息之后,究竟会调用何种方法需要在运行期间才能解析出来.那你也许会问:与给定的选择子名称相应的方法是不是也可以在runtime改变呢?没错,就是这样.若能善用此特性,则可 ...
- 格而知之16:我所理解的Block(2)
11.那么Block到底是怎么实现的呢?试一试通过将Block 的代码转换成普通C语言代码来查看它的实现过程. 要将OC代码转换成C语言代码,可以使用clang编译的一个命令: 通过这个命令能把指定文 ...
- 格而知之6:我所理解的Runtime(1)
基本简介 1.根据官方文档,OC有一个特性:它会尽可能把一些决定从编译时和链接时推迟到运行时才处理,所以这门语言需要的就不只是一个编译器,它还需要一个runtime系统来处理那些已经被编译过的代码. ...
- 格而知之16:我所理解的Block(3)
23.在前文中的例子中,Block结构体里的isa指针还没有详细讲解,这个指针都被置向了_NSConcreteStackBlock,它标识了Block的类型. 其实除了_NSConcreteStack ...
- 格而知之15:我所理解的Block(1)
1.Block 本质上是一个struct结构体,在这个结构体中,最重要的成员是一个函数(当然除函数外还有其他重要的成员). 2.在开始解析Block之前,首先来回顾一下Block的格式.Block相关 ...
- 格而知之8:我所理解的Runtime(3)
关联对象 14.使用Category对类进行拓展的时候,只能添加方法,而不适合添加属性(可以添加属性,也可以正常使用get方法和set方法,只是不会自动生成以下划线开头命名的成员变量). 可以通过关联 ...
- 格而知之5:我所理解的Run Loop
1.什么是Run Loop? (1).Run Loop是线程的一项基础配备,它的主要作用是来让某一条线程在有任务的时候工作.没有任务的时候休眠. (2).线程和 Run Loop 之间的关系是一一对应 ...
随机推荐
- WPF页面切换及弹窗
WPF页面切换及弹窗 结构如图: 效果如图: 代码如下: xaml <Window x:Class="PageChange.MainWindow" xmlns="h ...
- 在Struts2中使用Uploadify组件上传文件
Uploadify是一个基于Jquery的文件上传组件,官网http://www.uploadify.com/可以在官网获得该组件,运行演示示例,下载帮助文档. 作为Web前端的增强技术,Jq ...
- OSX: 私人定制Dock默认程序图标
不论什么一个新用户第一次登陆后,OSX都会自己主动地在用户的Dock中列出系统默认的应用程序图标,这些图标随着OSX版本号的不同而不同. 系统管理员有的时候须要改变这些系统默认图标,或者加入自己的或者 ...
- emacs window版环境配置(设置默认的.emacs文件,指向自定义.emacs达到自定义home的目的)
1.下载解压包 下载地址 ,下载之后我是直接解压到E:\emacs中的,E:\emacs中就有bin,libexec…等文件; 2.点击bin中的addpm.exe文件进行安装emacs; 3.就会 ...
- undefined和null的区别
在javascript中undefined和null几乎是没有区别的 undefined==null;//true; 区别: null是一个表示“无”的对象,转为数值为0:或者说没有对象,此处 ...
- (转)实例详解CSS中position的fixed属性使用
关于fixed属性,在什么情况下需要用,怎么用,首先,我们应该先了解下fixed属性的说明:fixed总是以body为定位时的对象,总是根据浏览器的窗口来进行元素的定位,通过"left&qu ...
- Python3.5入门学习记录-条件控制
Python的条件控制同C#一样,都是通过一条或多条语句的执行结果(True OR False)来决定执行的代码块. if 语句 Python中if语句的一般形式如下所示: if condition_ ...
- 如何循序渐进有效学习 JavaScript?
著作权归作者所有.商业转载请联系作者获得授权,非商业转载请注明出处.作者:宋学彦链接:http://www.zhihu.com/question/19713563/answer/23068003来源: ...
- 使用do...while的方法输入一个月中所有的周日
do{ var date = Number(prompt('请输入一个月的总天数')); var start = (prompt('请输入一个月的一号是周几')); for(var i=0;i< ...
- [翻译] C++ STL容器参考手册(第一章 <array>)
返回总册 本章节原文:http://www.cplusplus.com/reference/array/array/ 1. std::array (C++11支持) template < cla ...