Runtime - 消息发送原理
Runtime - 消息发送原理.
Objective-C运行时的核心就在于消息分派器objc_msgSend,消息分派器把选择器映射为函数指针,并调用被引用的函数。 要想理解objc_msgSend的背后原理,先来理解下NSInvocation这个类。
NSInvocation是命令模式的一种传统实现,它把一个目标、一个选择器、一个方法签名和所有的参数都塞进一个对象里,这个对象可以先存储起来,以备将来调用。当NSInvocation被调用时,它会发送信息,Objective-C运行时会找到正确的方法实现来执行。我们通过一个例子来理解下NSInvocation的作用,比如[NSObject alloc],此时会发送一个alloc消息,这条消息都包含什么内容呢?它怎么找到alloc的实现方法呢?这些都是通过NSInvocation来完成的,它包含了消息要传递的内容,也告诉了该怎么找到对应的方法实现。
解释一下什么是方法实现?一个方法实现(IMP)是一个指向具有如下签名的C函数的函数指针,注意是指针。
1
|
id function (id self, SEL _cmd, ...) |
NSInvocation包含了一个目标和选择器,目标是一个可接受的对象,选择器则是被发送的消息。比如[NSObject alloc],目标就是NSObject,选择器就是alloc。一个选择器大致是一个方法的名称,之所以说是大致是因为选择器不必精确映射到方法。比如[NSString length]和[NSData length]会映射到不同方法的实现,但他们拥有相同的选择器。
NSInvocation还包含一个方法签名(NSMethodSignature),它封装了一个方法的返回类型和参数类型,记住它不包括方法名称,只有返回类型和参数类型。你可以手动创建一个方法签名,如下:
1
|
NSMethodSignature *sig = [NSMethodSignature signatureWithObjCTypes: "@@:*" ]; |
但是应该尽可能少使用signatureWithObjCTypes:方法,获得方法签名常用的方法是为它请求一个类或实例,比如可以使用methodSignatureForSelector:方法从实例中请求实例方法签名,或者从类中请求类方法签名。也可以使用instanceMethodSignatureForSelector:方法从一个类中获取实例方法签名。两个方法有点绕口,我们通过一个例子来看下区别:
1
2
3
4
5
6
7
8
9
|
SEL initSEL = @selector(init); SEL allocSEL = @selector(alloc); // 从NSString类中获取实例方法(init)的方法签名 NSMethodSignature *initSig = [NSString instanceMethodSignatureForSelector:initSEL]; // 从test实例中获取实例方法(init)的方法签名 initSig = [@ "test" methodSignatureForSelector:initSEL]; // 从NSString类中获取类方法签名 NSMethodSignature *allocSig = [NSString methodSignatureForSelector:allocSEL]; |
最后,NSInvocation还包含了所有的参数。至此,对于[NSString length]和[NSData length]就可以通过NSInvocation对象包含的信息,找到它们分别对应的方法实现。我们来看一个具体的例子,如下:
1
2
3
4
5
6
7
8
9
10
11
12
|
NSMutableSet *set = [NSMutableSet set]; NSString *stuff = @ "stuff" ; SEL selector = @selector(addObject:); NSMethodSignature *sig = [set methodSignatureForSelector:selector]; 44 NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:sig]; [invocation setTarget:set]; [invocation setSelector:selector]; [invocation setArgument:&stuff atIndex:2]; [invocation invoke]; 44 NSLog(@ "set is : %@" , set); |
注意,第一个参数被置于索引2处,索引0是目标(self),索引1是选择器(_cmd),NSInvocation会自动设置它们。另外,必须把参数指针传递给参数,而不能传递参数本身。
接下来重点要介绍下消息传递是如何工作的?
在Objective-C中调用方法最终会翻译成调用方法实现的函数指针,并传递给这个方法实现一个对象指针、一个选择器和一组函数参数。每个Objective-C消息表达式都会转化为对objc_msgSend的调用,看下objc_msgSend的工作方式:
1. 检查接受对象是否为nil,如果是nil,调用nil处理程序。
2. 检查缓存中是不是已经有方法实现了,有的话,直接调用。
3. 比较请求的选择器和类中定义的选择器,如果找到了,调用方法实现。
4.比较请求的选择器和父类中定义的选择器,然后是父类的父类,以此类推,如果找到了选择器,调用方法实现。
5. 调用resolveInstanceMethod:(或resolveClassMethod)。如果它返回YES,那么重新开始。这一次对象会响应这个选择器,一般是因为它已经调用过class_addMethod。
6. 调用forwardingTargetForSelector:,如果返回非nil,那就把消息发送到返回的对象上,这里不要返回self,否则会形成死循环的。
7.调用methodSignatureForSelector:,如果返回非nil,创建一个NSInvocation并传给forwardInvocation:。
8. 调用doesNotRecognizeSelector:,默认的实现是抛出异常。
先看下第5步,首先可以想到的就是用resolveInstanceMethod:和resolveClassMethod:在运行时提供实现,这通常是@dynamic合成属性的处理方式。简单来说,就是需要自己实现属性的getter和setter方法,通过resolveInstanceMethod:方法来把setter方法和getter方法和属性绑定在一起。
如果第5步返回NO的话,系统接着会首先尝试一次快速转发,也就是调用forwardingTargetForSelector:,看其能否返回一个对象,如果有对象返回,就转发给返回的对象。快速转发的原理其实就是先从缓存里找下是否存在对应的选择器。
如果快速转发返回nil的话,接下来就进行普通的转发,调用forwardInvocation进行普通的转发。
objc_msgSend还有几个相关的函数:objc_msgSend_fpret、objc_msgSendSuper、objc_msgSend_stret、objc_msgSendSuper_stret。SendSuper格式的函数很明显是把消息发送给父类,而带stret的在返回结构体时处理大部分情况。在Intel处理器上返回浮点数时,带fpret的函数处理大部分情况。
方法混写(Method Swizzling)与ISA混写(Isa Swizzling)
在Objective-C中,混写(Swizzling)是指透明地把一个东西换成另一个,我们可以利用Objective-C中的运行时来实现混写。我们先看下方法混写,Objective-C提供了以下API来动态替换类方法或实例方法的实现:
class_replaceMethod替换类方法的定义。
method_exchangeImplementations交换两个方法的实现。
method_setImplementation设置一个方法的实现。
我们来看下三者的区别:
class_replaceMethod,当需要替换的方法有可能不存在时,可以考虑使用该方法。
method_exchangeImplementations,当需要交换两个方法的实现时使用。
method_setImplementation是最简单的用法,当仅仅需要为一个方法设置其实现方式时使用。
系统中提供的KVO使用到了isa混写,具体是怎么实现的呢?当你观察一个对象时,一个新的类会被自动创建,这个新类继承自该对象的原本的类,并且重写了被观察属性setter方法。重写setter方法会负责在调用原setter方法之前和之后,通知所有观察对象:值的更改。最后通过isa混写,把这个对象的isa指针指向这个新创建的子类,对象就神奇的变成了新创建的子类的实例。
注意一点:把isa指针指向新创建的子类,被观察的对象就变成了新创建子类的对象实例,这是由于isa指针永远指向其对应的类。用一张图来说明下:
键值观察通知依赖于NSObject的两个方法:willChangeValueForKey:和didChangeValueForKey:。在一个被观察属性发生改变之前,willChangeValueForKey:一定会被调用,这就会记录旧的值。而当改变之后,didChangeValueForKey:会被调用,继而obserValueForKey:ofObject:change:context:也会被调用。可以手动实现这些调用,但很少有人这么做。一般我们希望能控制回调的调用时机时才会这么做。
使用KVO的一个明显的优势就是零开销观察的优势,如果给定的实例没有观察者,那么KVO不会有任何消耗,因为根本没有KVO代码。而即使没有观察者,对于委托方法和NSNotification还得工作。
Runtime - 消息发送原理的更多相关文章
- runtime - 消息发送(objc_msgSend)
http://www.jianshu.com/p/95c8cb186673 在OC中,我们对方法的调用都会被转换成内部的消息发送执行对objc_msgSend方法的调用,掌握好消息发送,可以让我们在编 ...
- ActiveMQ(2)---ActiveMQ原理分析之消息发送
持久化消息和非持久化消息的发送策略 消息同步发送和异步发送 ActiveMQ支持同步.异步两种发送模式将消息发送到broker上.同步发送过程中,发送者发送一条消息会阻塞直到broker反馈一个确认消 ...
- ActiveMQ消息的发送原理
持久化消息和非持久化消息的发送策略:消息同步发送和异步发送 ActiveMQ支持同步.异步两种发送模式将消息发送到broker上.同步发送过程中,发送者发送一条消息会阻塞直到broker反馈一个确认消 ...
- Objective-C 消息发送与转发机制原理(摘)
八面玲珑的 objc_msgSend 此函数是消息发送必经之路,但只要一提 objc_msgSend,都会说它的伪代码如下或类似的逻辑,反正就是获取 IMP 并调用: id objc_msgSend( ...
- iOS 消息发送与转发详解
Objective-C 是一门动态语言,它将很多静态语言在编译和链接时期做的事情,放到了运行时来处理.之所以能具备这种特性,离不开 Runtime 这个库.Runtime 很好的解决了如何在运行时期找 ...
- Objective-C中的消息发送总结
关于OC中的消息发送的实现,在去年也看过一次,当时有点不太理解,但是今年再看却很容易理解. 我想这跟知识体系的构建有关,如果你不认识有砖.水泥等这些建筑的基本组成部分,那么我们应该很难理解建筑是怎么建 ...
- ActiveMQ点对点的消息发送案例
公司最近会用MQ对某些业务进行处理,所以,这次我下载了apache-activemq-5.12.0-bin把玩下. 基于练习方便需要,使用Windows的版本. 参考的优秀文章: activemq的几 ...
- runtime——消息机制
本文授权转载,作者:Sindri的小巢(简书) 从异常说起 我们都知道,在iOS中存在这么一个通用类类型id,它可以用来表示任何对象的类型 —— 这意味着我们使用id类型的对象调用任何一个方法,编译器 ...
- Unity 消息发送机制 解析
该博客,只为解析,解析,解析,已经整理好,已经整理好,已经整理好.代码核心原理套用网上最流行的那一套,也是最常用游戏开发适用的消息机制.这里面加上自己的一些优化,极大的修正(哈哈),实测,没问题.万一 ...
随机推荐
- 【Learning】积性函数前缀和——洲阁筛(min_25写法)
问题描述 洲阁筛解决的问题主要是\(n\)范围较大的积性函数前缀和. 已知一积性函数\(f(i)\),求\(\sum_{i=1}^nf(i)\). \(n\leq10^{12}\). 求解方法 如 ...
- loj2542 「PKUWC2018」随机游走 【树形dp + 状压dp + 数学】
题目链接 loj2542 题解 设\(f[i][S]\)表示从\(i\)节点出发,走完\(S\)集合中的点的期望步数 记\(de[i]\)为\(i\)的度数,\(E\)为边集,我们很容易写出状态转移方 ...
- 洛谷 P1306 斐波那契公约数 解题报告
P1306 斐波那契公约数 题意:求\(Fibonacci\)数列第\(n\)项和第\(m\)项的最大公约数的最后8位. 数据范围:\(1<=n,m<=10^9\) 一些很有趣的性质 引理 ...
- 彻底解决mac下terminal路径显示问题
mac 配色 mac shell配色 ~/.bash_profile是bash shell中当前登录用户的配置文件.bash是“终端”中默认的shell. alias ls=”ls -G”是给”ls ...
- Java的内存结构
Java中的内存结构 运行时数据区域的划分: 程序计数器(PC寄存器) 程序计数器(Program Counter Register)是一块较小的内存空间,可以看做是当前线程所执行的字节码的行号指示器 ...
- linux driver ------ 交叉工具链(cross toolchain)
在 ARM Linux 的开发中,人们趋向于使用 Linaro(http://www.linaro.org/)工具链团队维护的 ARM 工具链,它以每月一次的形式发布新的版本,编译好的可执行文件可以网 ...
- SVN报错:Node remains in conflict显示冲突的解决办法
如果是提示文件冲突: svn revert --depth=infinity 有冲突的文件名 如果提示目录有冲突: svn revert --depth=infinity 目录名 搞定.
- LigerUI下拉选择列表LigerComboBox中tree的节点初始化默认选中的问题
问题描述 用后台的Json传送tree的数据 前端用js方法来控制选中节点 此方法存在bug 实例: bug问题:无论设置的默认选中值是多少个,前台checkbox最多只显示选中一个,但是内容框中显示 ...
- golang 中的 time 包的 Ticker
真实的应用场景是:在测试收包的顺序的时候,加了个 tick 就发现丢包了 那么来看一个应用例子: package main import ( "fmt" "runtime ...
- python笔记之psutil模块
收集教程 http://www.cnblogs.com/xiao1/p/6164204.html 实战教程 安装psutil模块 pip2 install psutil 实战代码 #encoding= ...