深入理解Object-C消息转发机制

转载 2016年08月31日 11:27:32

最新在看runtime的相关技术,在浏览博客的时候看到了这篇不错的博文,推荐给大家。原文链接:http://coderperson.com/2015/06/08/iOS-runtime-messageForwarding/

相信大家对Object-C的消息传递机制并不陌生(如果不熟悉,我后续会再写一篇关于消息传递机制的文章),今天我来讲解另外一个重要的问题,就是对象在收到无法解读的消息之后会发生什么情况。

若想令类能理解某条消息,我们必须以程序码实现出对应的方法才行。但是,在编译器向类发送了其无法解读的消息并不会报错,因为在运行期可以继续向类中添加方法(动态添加),所以编译器在编译时还无法确知类中到底会不会有某个方法实现。当对象接收到无法解读的消息后,就会启动“消息转发”(message forwarding)机制,程序员可经由此过程告诉对象应该如何处理未知消息。

你可能早就遇到过经由消息转发流程所处理的消息了,只是未加留意。如果在控制台中看到下面这种提示信息,那就说明你曾向某个对象发送过一条无法解读的消息,从而启动了消息转发机制,并将次消息转发给了NSObject得默认实现。

1
2
3
4
5
-[__NSCFNumber lowercaseString]:unrecognized selector 
sent to instance 0x87
 *** Terminating app due to uncaught exception 
'NSInvalidArgumentException',reason:
'-[__NSCFNumber lowercaseString]:unrecognized selector sent to instance ox87'

上面这段异常信息是由NSObject的“doesNotRecognizeSelector:”方法所抛出的,此异常表明:消息接收者的类型是__NSCFNumber,而该接受者无法理解名位lowercaseString的选择子。本例所列举的这种情况并不奇怪,因为NSNumber类里本来就没有名为lowercaseString的方法。在本例中,消息转发过程以应用程序崩溃而告终,不过,开发者在编写自己的类时,可于转发过程中设置挂钩,用以执行预定的逻辑,而不使应用程序崩溃。

消息的转发分为两大阶段。第一阶段先征询接收者,所属的类,看其是否能动态添加方法,以处理当前这个“未知的选择子”(unknown selector),这叫做“动态方法解析”(dynamic method resolution)。第二阶段涉及“完整的消息转发机制”。如果运行期系统已经把第一阶段执行完了,那么接收者自己就无法再以动态新增方法的手段来响应包含该选择子的消息了。此时,运行期系统会请求接受者以其他手段来处理与消息相关的方法调用。这又细分为两小步。首先,请接受者看看有没有其他对象处理这条消息。若有,则运行期系统会把消息转给那个对象,于是消息转发过程结束,一起如常。若没有“备援的接收者”,则启动完整的消息转发机制,运行期系统会把于消息有关的全部细节都封装到NSInvocation对象中,再给接收者最后一次机会,令其设法解决当前还未处理的这条消息。

动态方法解析


对象在收到无法解读的消息后,首先将调用其所属类的下列类方法:

1
+ (BOOL)resolveInstanceMethod:(SEL)sel

该方法的参数就是那个未知的选择子,其返回值为Boolean类型,表示这个类是否能新增一个实例方法用以处理此选择子。在继续往下执行转发机制之前,本类有机会新增一个处理此选择子的方法。加入尚未实现的方法不是实例方法而是类方法,那么运行期系统就会调用另外一个方法

1
+ (BOOL)resolveClassMethod:(SEL)sel

使用这种办法的前提是:相关方法的实现代码已经写好,只等着运行的时候动态插在类里面就可以了。此方案常用来实现@dynamic属性。

备援接收者


当前接收者还有第二次机会能处理未知的选择子,在这一步中,运行期系统会问它:能不能把这条消息转给其他接收者来处理。与该步骤对应的处理方法如下:

1
- (id)forwardingTargetForSelector:(SEL)aSelector

方法参数代表未知的选择子,若当前接收者能找到被援对象,则将其返回,若找不到就返回nil。在一个对象内部,可能还有一系列其他对象,该对象可经由此方法将能够处理某选择子的相关内部对象返回,这样的话,在外界看来,好像是该对象亲自处理了这些消息似的。

完整的消息转发


如果转发算法已经来到这一波的话,那么唯一能做的就是启用完整的消息转发机制了。首先创建NSInvocation对象,把与尚未处理的那条消息有关的全部细节都封于其中。在触发NSInvocation对象时,“消息派发系统”将亲自出马,把消息指派给目标对象。

此步骤会调用下列方法来转发消息:

1
-(void)forwardInvocation:(NSInvocation *)invocation

这个方法实现很简单:只需改变调用目标,使消息在新目标上得以调用即可。然而这样实现出来的方法与“备援接收者”方案实现的方法等效,所以很少有人采用这么简单的实现方式。

以完整的例子演示动态方法解析


下面编写一个类似于“字典”的对象。

在接口文件中随便定义几个属性:

1
2
3
4
@interface GXZDictionary : NSObject
@property (nonatomic,strong) NSString *name;
@property (nonatomic,strong) NSNumber *number;
@property (nonatomic,strong) NSDate *date;

本例的关键在于resolveInstanceMethod:方法的实现代码:

1
@dynamic name,number,date;
1
2
3
4
5
6
7
8
9
10
 + (BOOL)resolveInstanceMethod:(SEL)selector
{ 
    NSString *selectorString = NSStringFromSelector(selector);
    if ([selectorString hasPrefix:@"set"]) {
        class_addMethod(self, selector, (IMP)autoDictionarySetter, "v@:@");
    } else {
        class_addMethod(self, selector, (IMP)autoDictionaryGetter, "@@:");
    }
    return YES;
}

然后就是Getter和Setter方法实现了:

getter:

1
2
3
4
5
6
7
id autoDictionaryGetter(id self,SEL _cmd)
{
    GXZDictionary *typedSelf = (GXZDictionary *)self;
    NSMutableDictionary *backingStore = typedSelf.backingStore;
    NSString *key = NSStringFromSelector(_cmd);
    return [backingStore objectForKey:key];
}

其中的backingStore是实现文件中定义的可变的字典,用于存储数据。

下面是Setter方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
void autoDictionarySetter(id self,SEL _cmd,id value)
{
    GXZDictionary *typedSelf = (GXZDictionary *)self;
    NSMutableDictionary *backingStore = typedSelf.backingStore;
    NSString *selectorString = NSStringFromSelector(_cmd);
    NSMutableString *key  = [selectorString mutableCopy];
    [key deleteCharactersInRange:NSMakeRange(key.length - 1, 1)];
    [key deleteCharactersInRange:NSMakeRange(0, 3)];
    NSString *lowercaseFirstChar = [[key substringToIndex:1] lowercaseString];
    [key replaceCharactersInRange:NSMakeRange(0, 1) withString:lowercaseFirstChar];
    if (value) {
        [backingStore setObject:value forKey:key];
    } else {
        [backingStore removeObjectForKey:key];
    }
}

好了,主要工作已经完成,下面直接使用就可以了,调用很简单:

1
2
3
GXZDictionary *dict = [GXZDictionary new];
dic.date = [NSDate dateWithTimeIntervalSince1970:475372800];
NSLog(@"%@",dict.date);

好了,是不是很神奇,很简单,现在可以随便在接口文件中添加属性了。

小结


  • 若对象无法响应某个选择子,则进入消息转发流程。
  • 通过运行期的动态方法解析功能,我们可以在需要用到某个方法时再将其加入类中。
  • 对象可以把其无法解读的某些选择子转交给其他对象处理。
  • 经过上述两步之后,如果还是没办法处理选择子,那就启动完整的消息转发机制。

相关文章推荐

理解Objective-C中的消息发送

首先来看看怎么理解发送消息的含义:曾经觉得Objc特别方便上手,面对着 Cocoa 中大量 API,只知道简单的查文档和调用。还记得初学 Objective-C 时把[receiver message...

深入理解Object-C消息转发机制

相信大家对Object-C的消息传递机制并不陌生(如果不熟悉,我后续会再写一篇关于消息传递机制的文章),今天我来讲解另外一个重要的问题,就是对象在收到无法解读的消息之后会发生什么情况。 若想令类能理...
  • sharpyl
  • sharpyl
  • 2017年01月09日 17:30
  • 231

为什么Objective-C的对象“调用方法”叫做发消息。

Objective-C与C++,java等面向对象的语言类似,但是在使用Objective-C的类创建对象,并且这个对象“调用自身类的方法的时候”,我们叫做给对象发送一个消息,而不是调用方法,这就是O...

Object-c消息之运行时动态绑定

Object-c消息之运行时动态绑定 1,简介 在Objective-C中,message在执行阶段绑定,转换成对objc_msgSend方法的调用。objc_msgSend方法含两个必要参数:r...

object-c 学习第三天(方法和消息)

从今天开始我还是以翻译为主,以后慢慢理解 官方原文地址: https://developer.apple.com/library/mac/#referencelibrary/GettingStar...

Object-C的函数调用机制详解--消息

在ObjC 中则如下:  Bird * aBird = [[Bird alloc] init];  [aBird fly];   初看起来,好像只是书写形式不同...

第一篇 Object-C快速入门

Object-C是OS X和iOS上的主要程序开发语言。它是C语言的超集,提供了面向对象和动态运行时的功能。Object-C继承了C语言的语法、基本类型和流程控制语句,同时添加了定义类和方法的语法。当...

Objective-C消息机制的原理

转载:http://desheng.me/2012/03/31/objective-c%E6%B6%88%E6%81%AF%E6%9C%BA%E5%88%B6%E7%9A%84%E5%8E%9F%E7...
  • chun799
  • chun799
  • 2012年09月10日 22:35
  • 4870

Object-C的函数调用机制详解--消息

本文系转载,原文出处:http://blog.csdn.net/kesalin ps:Object-C和C,C++,java的函数调用机制还是有所区别的,其完全的runtime的调用方式是其实现消息...
  • likendsl
  • likendsl
  • 2012年05月14日 18:39
  • 15063

Object-C的消息传递机制和method swizzling方法混淆

objc_msgSend 在Object-C中,我们经常调用一个对象的方法,通常我们将这个过程成为 消息传递。不同于 C 语言对对象方法的静态调用,Object-C 是通过 Dynamic Bind...
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:深入理解Object-C消息转发机制
举报原因:
原因补充:

(最多只允许输入30个字)