消息发送(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)的更多相关文章

  1. 格而知之3:Core Data的基本使用

    最近准备做一个随手笔记类的app给自己用,考虑到从未使用过Core Data,就决定用Core Data来做数据存储.在网上参考了一些Core Data的资料后,用一天的时间写了这个demo,主要测试 ...

  2. 理解 Objective-C Runtime

    初学 Objective-C(以下简称ObjC) 的人很容易忽略一个 ObjC 特性 —— ObjC Runtime.这是因为这门语言很容易上手,几个小时就能学会怎么使用,所以程序员们往往会把时间都花 ...

  3. 理解Objective-C Runtime(四)Method Swizzling

    Objective-C对象收到消息之后,究竟会调用何种方法需要在运行期间才能解析出来.那你也许会问:与给定的选择子名称相应的方法是不是也可以在runtime改变呢?没错,就是这样.若能善用此特性,则可 ...

  4. 格而知之16:我所理解的Block(2)

    11.那么Block到底是怎么实现的呢?试一试通过将Block 的代码转换成普通C语言代码来查看它的实现过程. 要将OC代码转换成C语言代码,可以使用clang编译的一个命令: 通过这个命令能把指定文 ...

  5. 格而知之6:我所理解的Runtime(1)

    基本简介 1.根据官方文档,OC有一个特性:它会尽可能把一些决定从编译时和链接时推迟到运行时才处理,所以这门语言需要的就不只是一个编译器,它还需要一个runtime系统来处理那些已经被编译过的代码. ...

  6. 格而知之16:我所理解的Block(3)

    23.在前文中的例子中,Block结构体里的isa指针还没有详细讲解,这个指针都被置向了_NSConcreteStackBlock,它标识了Block的类型. 其实除了_NSConcreteStack ...

  7. 格而知之15:我所理解的Block(1)

    1.Block 本质上是一个struct结构体,在这个结构体中,最重要的成员是一个函数(当然除函数外还有其他重要的成员). 2.在开始解析Block之前,首先来回顾一下Block的格式.Block相关 ...

  8. 格而知之8:我所理解的Runtime(3)

    关联对象 14.使用Category对类进行拓展的时候,只能添加方法,而不适合添加属性(可以添加属性,也可以正常使用get方法和set方法,只是不会自动生成以下划线开头命名的成员变量). 可以通过关联 ...

  9. 格而知之5:我所理解的Run Loop

    1.什么是Run Loop? (1).Run Loop是线程的一项基础配备,它的主要作用是来让某一条线程在有任务的时候工作.没有任务的时候休眠. (2).线程和 Run Loop 之间的关系是一一对应 ...

随机推荐

  1. pyqt最小化学习

    # -*- coding: cp936 -*- #!/usr/bin/env python # -*- coding:utf-8 -*- from PyQt4 import QtCore, QtGui ...

  2. yii使用寻呼功能

    CDbCriteria这是类包使用,包是yii自带专门用来处理类似分类这种功能的. 而我们使用yii框架然后调用这种方法会起到事半功倍的效果,会发现使用这个可以节省非常多的时间.让你高速的使用PHP中 ...

  3. 域名解析 URL转发

    URL转发 转发功能:如果您没有一台独立的服务器(也就是没有一个独立的IP地址)或者您还有一个域名B,您想访问A域名时访问到B域名的内容,这时您就可以通过URL转发来实现.url转发可以转发到某一个目 ...

  4. oracle函数Lpad与Rpad

    函数介绍 lpad函数从左边对字符串使用指定的字符进行填充.从其字面意思也可以理解,l是left的简写,pad是填充的意思,所以lpad就是从左边填充的意思. 语法格式如下: lpad( string ...

  5. HTML与CSS入门——第二章 发布Web内容

    知识点: 1.使用文本编辑器创建一个基本的HTML文件的方法 2.使用FTP将文件传送到你的Web服务器的方法 3.文件在Web服务器上应该存储的位置 4.在没有Web服务器的情况下分发Web内容的方 ...

  6. CSS 相关知识总结

    1 什么是CSS? CSS全称(Cascading Style Sheets)是一门指定文档该如何呈现给用户的语言. 2 为何使用CSS? CSS 文档信息的内容和如何展现它的细节想分离,文档细节即为 ...

  7. gdal_merge.py

    1 gdal_merge.py: 合并(Merge)/镶嵌(Mosaic)工具.要求图像必须是相同坐标系统.具有相同的波段数:可以不同分辨率,可以有重叠区域(后加入图像覆盖先加入的图像). 注意:只能 ...

  8. HTML5 本地裁剪图片并上传至服务器(转)

    很多情况下用户上传的图片都需要经过裁剪,比如头像啊什么的.但以前实现这类需求都很复杂,往往需要先把图片上传到服务器,然后返回给用户,让用户确定裁剪坐标,发送给服务器,服务器裁剪完再返回给用户,来回需要 ...

  9. Button简单实例1

    1.XML按钮定义 <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" an ...

  10. [翻译] C++ STL容器参考手册(第一章 <array>)

    返回总册 本章节原文:http://www.cplusplus.com/reference/array/array/ 1. std::array (C++11支持) template < cla ...