再谈消息转发

之前写了篇关于消息发送的文章,其中提及到了消息转发,由于 OC 是一门动态的语言,在编译时,编译器还无法确定到底有没有这个方法,因为在运行时我们还可以动态的给它添加对应的方法处理,我曾说过,当一个方法在自己或其继承结构上面的方法列表中都没有找到,就会启动对应的 "消息转发" 机制。

好,现在我们开始好好说说 "消息转发" 究竟是怎么一回事。总得来说,消息转发的流程分为两大阶段。先来说第一阶段,在第一阶段时,当接受者无法处理发送的消息时,首先会触发当前类的 resolveInstanceMethod: 或者对应的 resolveClassMethod: 方法,在这个方法中会询问接受者,是否能够动态的给它添加对应的方法来处理这个未知的消息,这个行为的专业术语叫 "动态方法解析",在这个方法中,系统会将未处理的那个 @selector(方法) 名传递给我们,然后让我们返回一个 BOOL 值,询问是否能够添加一个实例或者类方法来处理它,当我们直接返回一个 NO ,或者直接调用父类方法让父类处理它时,自然就会崩掉,举个例子:

Person *p = [[Person alloc]init];
 objc_msgSend(p,@selector(eat));

在上面的代码中,Person 这个类里是没有 eat 这个实例方法的,再看下面:

@implementation Person
 + (BOOL)resolveInstanceMethod:(SEL)sel{
return [super resolveInstanceMethod:sel];
}
@end 

在上面的代码中,我直接调用了父类的处理方法,当然,这其实和不写是没有区别的,我们现在看看运行后的报错信息

reason: '-[Person eat]: unrecognized selector sent to instance 0x7ff64350fdf0'

不管英语是好是差,作为一个程序员,看到这种报错信息,哪怕你不懂英语也能看出来错误原因了吧 "向实例发送了一个未识别的消息" ,那个 0x7ff64350fdf0 也就是当前 p 对象的地址,其实报错信息里还有一条重要的信息:

0   CoreFoundation                      0x0000000105896d85 __exceptionPreprocess + 165
1   libobjc.A.dylib                     0x000000010530adeb objc_exception_throw + 48
2   CoreFoundation                      0x000000010589fd3d -[NSObject(NSObject) doesNotRecognizeSelector:] + 205

其中的 [NSObject(NSObject) doesNotRecognizeSelector:] + 205 这个方法应该说是消息传递过程中的最后一步,当没有任何措施处理这个未识别的消息,便会调用 NSObject的doesNotRecognizeSelector:方法默认实现来抛出异常。
那么,如何在第一阶段来拦截这个消息转发的过程,进行 "动态方法解析呢" ? OK,现在我们来试一下:

 @implementation Person
void change(){
NSLog(@"我是被添加的方法");
}

+ (BOOL)resolveInstanceMethod:(SEL)sel{

class_addMethod(self, sel, (IMP)change, "v@:");

return YES;
}
@end

在上面的代码中,我给它创建了一个 change 函数,在 resolveInstanceMethod 方法中给它添加进了 class_addMethod 这个函数,这个函数中,第一个参数是需要添加方法的类,第二个参数是需要添加的方法名,第三个参数是指向函数的IMP指针,第四个参数是待添加方法的类型编码(类型编码:编码开头的字符表示方法的返回值类型,后面的字符表示各个参数),并且将 resolveInstanceMethod: 方法返回了 YES ,表示能够动态的添加方法来处理这个未识别的消息,这样一来,控制台便会打印 change 函数中打印的那段字符串。

好了,刚刚前面部分其实说的都是第一阶段,那部分报错信息应该说是第二阶段的末尾部分,大家先别关注,接着往下看。
现在说第二阶段,如果在第一阶段时,接收者没有进行 "动态方法解析" ,那么就会进入第二阶段,这时接收者已经无法再利用动态添加方法来处理未识别的消息了,但是,这时候接收者也可以用其他手段来处理这条消息,这时的第二阶段又得分为两步,第一步,运行时系统会询问接收者是否有其他对象来帮助其处理这条消息, 具体会调用当前类的如下方法进行询问:
- (id)forwardingTargetForSelector:(SEL)aSelector{
}

从上面的代码中可以看到,该方法会返回一个 id 类型的对象,意思就是,如果有其他对象可以帮助它处理这条消息,就将那个对象返回,如果没有,就返回 nil,但是,请注意,刚刚也说了,在这第二阶段,已经没有办法在这里给它动态添加方法了,所以千万不要把其写在这个方法中。

经常刚刚的第二阶段的第一步操作后,如果已经返回了可以处理该消息的对象,那么该消息就被处理,过程便结束。但是如果返回的是 nil, 那么就会进入到第二步,也就是启动 "完整的消息转发机制" 了,这时,首先会创建一个 NSInvocation 类型的对象,可以点到头文件去看,这个对象里包含了 selector 、target(调用目标) 以及参数,当创建了这个对象,运行时会把消息传递给这个对象中,并且调用下面的方法来转发消息:
- (void)forwardInvocation:(NSInvocation *)anInvocation{

}

在上面的代码中,你只需要在实现里改变 anInvocation 对象的 target (调用目标) 属性,让消息的在新的目标上得到实现即可,其实这个跟上面的第一步是差不多的,也很少有人用到这些,如果依然没有新的目标来处理这个消息,这个类就会按照继承体系,一直向上寻找有没有父类及以上处理了forwardInvocation:这个方法,直至 NSObject 类,如果一直到NSObject 类之前都没有哪个类来处理,那么调用完 NSObject 类这个方法后,继而调用 NSObject 类的 doesNotRecognizeSelector 方法,抛出异常,表明这个消息最终没有被处理。
消息转发部分我要说的基本就这么多,下面这张图很好的表达了消息转发的整个过程:

Snip20160830_3.png

其实,我们在开发中基本上不需要管第二阶段的那些流程,最好的方式都是在第一阶段中的动态添加方法中给它处理好,当然,笔者说的这些都是自己的看法,有不对的地方也希望各位能够指出,👋

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,242评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,769评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,484评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,133评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,007评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,080评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,496评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,190评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,464评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,549评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,330评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,205评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,567评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,889评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,160评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,475评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,650评论 2 335

推荐阅读更多精彩内容