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];44NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:sig];[invocation setTarget:set];[invocation setSelector:selector];[invocation setArgument:&stuff atIndex:2];[invocation invoke];44NSLog(@"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 消息发送机制 解析
该博客,只为解析,解析,解析,已经整理好,已经整理好,已经整理好.代码核心原理套用网上最流行的那一套,也是最常用游戏开发适用的消息机制.这里面加上自己的一些优化,极大的修正(哈哈),实测,没问题.万一 ...
随机推荐
- c++11 智能指针 unique_ptr、shared_ptr与weak_ptr
c++11 智能指针 unique_ptr.shared_ptr与weak_ptr C++11中有unique_ptr.shared_ptr与weak_ptr等智能指针(smart pointer), ...
- 【CF472G】Design Tutorial: Increase the Constraints
Description 给出两个01序列\(A\)和\(B\) 要求回答\(q\)个询问每次询问\(A\)和\(B\)中两个长度为\(len\)的子串的哈明距离 哈明距离的值即有多少个位置不相等 ...
- 网络对抗课题4.3.1 SQL注入原理与实践
网络对抗课题4.3.1 SQL注入原理与实践 原理 SQL注入漏洞是指在Web应用对后台数据库查询语句处理存在的安全漏洞.也就是,在输入字符串中嵌入SQL指令,在设计程序中忽略对可能构成攻击的特殊字符 ...
- PHP获取IP地址的方法,防止伪造IP地址注入攻击
PHP获取IP地址的方法 /** * 获取客户端IP地址 * <br />来源:ThinkPHP * <br />"X-FORWARDED-FOR" 是代理 ...
- 解题:CQOI 2013 和谐矩阵
题面 踩踩时间复杂度不正确的高斯消元 首先可以发现第一行确定后就可以确定整个矩阵,所以可以枚举第一行的所有状态然后$O(n)$递推检查是否合法 $O(n)$递推的方法是这样的:设$pre$为上一行,$ ...
- MVC中HTML控件设为只读readonly
http://www.th7.cn/web/html-css/201501/78934.shtml 1.下拉框设为只读试了试用这个有效: @Html.DropDownListFor(model =&g ...
- C#线程篇---解答线程之惑(2)
我们都知道,在这个行业,追求的就是用最少的时间学最多的知识,这是我写这个系列最想达到的目标,在最快的时间内,帮助更多的人学习更多的线程知识. 前一篇,讲述了线程基础,给大家铺垫了一个基础,这一篇着重介 ...
- git更换 拉取推送地址
更换:git remote set-url originhttp://git.caomall.cn/sucry/mouse.git」
- Lvs+Keepalived实现MySQL高可用
LVS+Keepalived+MySQL高可用配置 本文所有配置前提是已实现MySQL双主备份(MySQL双主) 安装前的准备: VIP:192.168.0.201 Keepalived: Keepa ...
- JS获取FckEditor的值
不需要在页面引用任何额外的JS文件 //获取编辑器中HTML内容 function getEditorHTMLContents(EditorName) { var oEditor = FCKedito ...