理解Objective-C Runtime(三)消息转发机制
消息转发机制概述
上一篇博客消息传递机制中讲解了Objective-C中对象的「消息传递机制」。本文需要讲解另外一个重要问题:当对象受到无法处理的消息之后会发生什么情况?
显然,若想令类能理解某条消息,我们必须以程序代码实现出对应的方法才行。但是,在编译期向类发送了其无法理解解读的消息并不会报错,因为在运行期间允许继续向类中添加方法,所以,编译器在编译期间还无法确知类中到底会不会有个方法的实现。当对象接收到无法理解的消息后,就会启动「消息转发」(message forwarding)机制,用户(程序员)可经此过程告诉对象应该如何处理未知消息。
向对象发送它无法理解的后果在实际开发中我们会经常遇到,如下:
- (void)viewDidLoad {
[super viewDidLoad];
NSObject *aObject = [[NSObject alloc] init];
NSLog(@"%@", [(NSString *)aObject lowercaseString]);
}
显然,这几行代码可以通过编译,但是在运行时会出现如下错误,并导致崩溃:
-[NSObject lowercaseString]: unrecognized selector sent to instance 0x7a8acba0
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[NSObject lowercaseString]: unrecognized selector sent to instance 0x7a8acba0'
*** First throw call stack:
(
0 CoreFoundation 0x008f4746 __exceptionPreprocess + 182
1 libobjc.A.dylib 0x0057da97 objc_exception_throw + 44
2 CoreFoundation 0x008fc705 -[NSObject(NSObject) doesNotRecognizeSelector:] + 277
3 CoreFoundation 0x00843287 ___forwarding___ + 1047
4 CoreFoundation 0x00842e4e _CF_forwarding_prep_0 + 14
...
...
上面这段异常信息是由NSObject的doesNotRecognizeSelector:方法抛出的,此异常表明:消息的接收者的类型是NSObject(即receiver是NSObject类型对象),而该接收者无法理解名为lowercaseString的选择子。
在本例中,消息转发过程以应用程序崩溃告终,不过,开发者在编写自己的类时,可于转发过程中设置挂钩,用于执行预定的逻辑,而不使得应用程序崩溃。
当对象接受到「未知的选择子」(unknown selector)时,开启「消息转发」,这分为两大阶段:
- 第一阶段先与接收者所属的类打交道,看其是否能动态添加方法,以处理当前这个「未知的选择子」,这叫『动态方法解析』(dynamic method resolution);
- 第二阶段涉及『完整的消息转发机制』(full forwarding mechanism)。如果runtime系统已经把第一阶段执行完了,那receiver自己就无法再以动态新增方法的手段来响应包含该选择子的消息了。此时,运行期系统会请求receiver用其他手段来处理这条消息相关的方法调用了。这又细分为为两小步:
- 请receiver看看有没有其他对象能处理这条消息,若有,则runtime系统会把消息转发给那个对象,消息转发结束;
- 若有没有「备援的接收者」(replacement receiver),则启动『完整的消息转发机制』,runtime系统会把与消息有关的全部细节封装到NSInvocation对象中,再给接收者最后一次机会,令其设法解决当前还未处理的这条消息。
Receiver在收到unknown selector后,首先将调用其本类的resolveInstanceMethod:方法,该方法定义如下:
+ (BOOL)resolveInstanceMethod:(SEL)sel;
该方法的参数就是那个unknown selector,其返回值为Boolean类型,表示这个类是否能新增一个实例方法用以处理该unknown selector。在继续往下执行转发机制之前,本类有机会新增一个处理此selector的方法。所以resolveInstanceMethod:的一般使用套路是:
+ (BOOL)resolveInstanceMethod:(SEL)aSelector {
if (/* aSelector满足某个条件 */) {
/*
调用class_addMethod为该类添加一个处理aSelector的方法,譬如:
class_addMethod(self, aSelector, aImp, @"v@:@");
*/
return YES;
}
return [super resolveInstanceMethod:aSelector];
}
假如尚未实现的方法不是实例方法而是类方法,那么runtime系统会调用另外一个与resolveInstanceMethod:类似的方法resolveClassMethod:,其原型如下:
+ (BOOL)resolveClassMethod:(SEL)sel;
举个列子:
@interface Student : NSObject @property (nonatomic, strong, getter=name, setter=setName:) NSString *name; @end @implementation Student @dynamic name; // 注意,这是C语言函数(不是Objective-C方法)
id name(id self, SEL _cmd) {
return @"张不坏";
} // 注意,这是C语言函数(不是Objective-C方法)
void setName(id self, SEL _cmd, id value) {
NSLog(@"do nothing");
} + (BOOL)resolveInstanceMethod:(SEL)sel { if (sel == @selector(name)) {
class_addMethod(self, sel, (IMP)name, "@@:");
return YES;
} else if (sel == @selector(setName:)) {
class_addMethod(self, sel, (IMP)setName, "v@:@:");
return YES;
}
return [super resolveInstanceMethod:sel];
}
“@@:”和”v@:@:”用来描述函数参数和返回值,更多内容参考Type Encodings和这里。
执行效果:
Student *jason = [[Student alloc] init];
jason.name = @"Jason";
NSLog(@"name of this student:%@", jason.name); /* 输出:
do nothing
name of this student:张不坏
*/
备援接收者
当前receiver还有第二次机会能处理unknown selector,在这一步中,runtime系统会问它:能不能把这条消息转给其他对象来处理?与该步骤对应的处理方法是forwardingTargetForSelector,该方法定义于<objc/NSObject.h>中:
- (id)forwardingTargetForSelector:(SEL)aSelector;
方法参数代表unknown selector,若当前receiver能找到备援对象,则将其返回,若找不到,则返回nil。
通过此方案,我们可以通过『组合』(composition)来模拟出『多继承』(multiple inheritance)的某些特性。
所谓『多继承』指一个类可以继承自多个类,即该类对象具有多个类的属性和方法,譬如A继承自B和C,则A的类对象a同时具有了B和C的方法和属性。
而Objective-C是不支持多继承了。因此A只能继承自B,而不能同时也继承C。若想让A也具备C的方法,基于『消息转发机制』的实现过程是这样的:在A中定义一个C对象(假设为c),当向A对象发送C才能处理的消息时,在A的- (id)forwardingTargetForSelector:方法实现中返回c(C对象)即可。如此这般,在外界看来,就感觉A能够处理这些C中定义的方法。
为了更好的阐述「通过转发模拟多继承」,以下图举个例子:

在上图中,Warrior(武士)和Diplomat(外交官)没有继承关系,所以它自然不能处理Diplomat才能做的事情negotiate(谈判)。但是,通过「消息转发」,可以让Warrior也能够接受negotiate消息。具体做法是在Warrior中定义一个Diplomat对象(内部变量,假设名为aDiplomat),当Warrior对象接受到negotiate消息时,就转发给aDiplomat。这让人感觉武士(Warrior)也兼具谈判(negotiate)能力。
完整的消息转发机制
如果转发已经到了这一步的话,那么唯一能够做的就是启用『完整的消息转发机制』了。首先创建NSInvocation对象,将未知消息相关的全部细节都封装于其中。此对象包含选择子、目标(target)以及参数。在触发NSInvocation对象时,『消息派发系统』(message-dispatch system)将亲自出马,把消息派给目标对象。
此步骤会调用forwardInvocation方法来转发消息,该方法定义于<objc/NSObject.h>中:
- (void)forwardInvocation:(NSInvocation *)anInvocation;
这个方法可以实现得很简单:只要改变调用目标,是消息在新目标上得以调用即可。然而这样实现出来的方法与『备援接收者』方案所实现的方法等效,所以很少有人采用这种实现方式。比较有用的实现方式为:在触发消息前,先以某种方式改变消息内容,比如追加另外一个参数,或是改换选择子,等等。
实现此方法时,若发现某调用方法不应由本类处理,则需调用超类的同名方法。这样的话,集成体系中的每个类都有机会处理此调用请求,直至NSObject。如果最后调用了NSObject类的方法,那么该方法还有继而调用doesNotRecognizeSelector:以抛出异常,此异常表明选择子最终未能得到处理。
消息转发全流程
下图是消息转发全流程图,描述了「消息转发机制」的各个步骤。

Receiver在每一步中均有机会处理消息。步骤越往后,处理消息的代价就越大;最好能在第一步就处理完,这样的话,runtime系统就可以将此方法缓存起来,进而提高效率。若想在第三步里把消息转发给备援的receiver,那还不如把转发操作提前到第二步。因为第三步只是修改了调用目标,这项改动放在第二步会更为简单,不然的话,还得创建并处理完整的NSInvocation。
理解Objective-C Runtime(三)消息转发机制的更多相关文章
- iOS Runtime的消息转发机制
前面我们已经讲解Runtime的基本概念和基本使用,如果大家对Runtime机制不是很了解,可以先看一下以前的博客,会对理解这篇博客有所帮助!!! Runtime基本概念:https://www.cn ...
- OC:浅析Runtime中消息转发机制
一.介绍 OC是一门动态性语言,其实现的本质是利用runtime机制.在runtime中,对象调用方法,其实就是给对象发送一个消息,也即objc_msgSend().在这个消息发送的过程中,系统会进行 ...
- iOS runtime探究(二): 从runtime開始深入理解OC消息转发机制
你要知道的runtime都在这里 转载请注明出处 http://blog.csdn.net/u014205968/article/details/67639289 本文主要解说runtime相关知识, ...
- Effective Objective-C 2.0 — 第12条:理解消息转发机制
11 条讲解了对象的消息传递机制 12条讲解对象在收到无法解读的消息之后会发生什么,就会启动“消息转发”(message forwarding)机制, 若对象无法响应某个选择子,则进入消息转发流程. ...
- runtime之消息转发
前言 在上一篇文章中我们初尝了runtime的黑魔法,可以在程序编译阶段就获取到成员变量的名字,特性以及动态的给对象增加属性等等,在接下来中我们进一步了解OC的消息发送机制.如果之前没接触过runti ...
- iOS的消息转发机制详解
iOS开发过程中,有一类的错误会经常遇到,就是找不到所调用的方法,当然这类问题比较好解决,给当前对象或其父类对象添加该方法即可,使得编译器在编译时能正确找到该方法:或者,还有另外的方法,由于Objec ...
- iOS 消息转发机制
这篇博客的前置知识点是 OC 的消息传递机制,如果你对此还不了解,请先学习之,再来看这篇.这篇博客我尝试用口语的方式像讲述 PPT 一样给大家讲述这个知识点. 我们来思考一个问题,如果对象在收到无法解 ...
- iOS消息转发机制
iOS消息转发机制 “消息派发系统”(message-dispatch system) 若想令类能够理解某条消息,我们必须实现出对应的方法才行.但是,在编译器向类发送其无法解读的消息时并不会报错,因为 ...
- runtime消息转发机制
Objective-C 扩展了 C 语言,并加入了面向对象特性和 Smalltalk 式的消息传递机制.而这个扩展的核心是一个用 C 和 编译语言 写的 Runtime 库.它是 Objective- ...
随机推荐
- golang测试框架--smartystreets/goconvey
视频教程和配套博客:goconvey - 课时 1:优雅的单元测试 Go 语言虽然自带单元测试功能,在 GoConvey 诞生之前也出现了许多第三方辅助库.但没有一个辅助库能够像 GoConvey 这 ...
- vba功能语句
VBA语句集(第1辑) 定制模块行为(1) Option Explicit '强制对模块内所有变量进行声明Option Private Module '标记模块为私有,仅对同一工程中其它模块有用,在宏 ...
- ****如何优雅的用Axure装逼?高保真原型心得分享
本文核心内容点:- 啥是高保真原型?(附简单说明原型)- Axure可以画出什么水准的高保真?(给示例,开启装逼模式)- 高保真原型图技巧:- 啥时候上高保真?适用场景 and 不适用场景 啥是高保真 ...
- [BOI2007] Mokia
题目描述 摩尔瓦多的移动电话公司摩基亚(Mokia)设计出了一种新的用户定位系统.和其他的定位系统一样,它能够迅速回答任何形如“用户C的位置在哪?”的问题,精确到毫米.但其真正高科技之处在于,它能够回 ...
- sourcetree帮助文档
Overview SourceTree可以在bookmarks界面跟踪所有的git和mercurial项目.可以概览工程中是否有需要提交的文件等.添加新的bookmark很简单,可以通过两种方式,通过 ...
- XML Publisher Template Type - Microsoft Excel Patch
XML Publisher Template Type - Microsoft Excel Patch Oracle XML Publisher > Templates > Create ...
- 紫书p199 八数码(BFS,hash)
八数码问题 紫书上的简单搜索 渣渣好久才弄懂 #include<cstdio> #include<cstring> using namespace std; const i ...
- XUtils BitmapUtils 改造以加入drawable支持
=== XUtilsBitmapUtils 改造以加入drawable支持 === # XUtils 简单介绍 XUtils 是一套少有的早期国产安卓框架, 其源于AFinal, 文件夹结构也与之相似 ...
- Android studio 百度地图开发(2)地图定位
Android studio 百度地图开发(2)地图定位 email:chentravelling@163.com 开发环境:win7 64位,Android Studio,请注意是Android S ...
- USB/IP项目总结
青云最近推出了云桌面功能,用户可以像使用本地计算机一样访问远程主机,支持USB重定向,不禁让我想起了2年前调试的一个开源项目USB/IP,当时还用英文写了一个总结性文档,放在这里方便以后查看. ...