OC底层原理14-消息转发机制

我们在 OC底层原理13-动态方法决议 一文中,分析了动态方法决议,调试的时候发现resolveInstanceMethodresolveClassMethod方法如果在类中重写,但是没有添加方法的时候,这2个方法分别会被调用2次,然后才会崩溃,为什么被调了2次呢?抱着这个疑问开始了源码断点调试方法执行顺序的研究

一、方法慢速查询 -> 动态方法决议 -> 消息转发流程图

消息转发机制.jpg

二、快速消息转发forwardingTargetForSelector

2.1 forwardingTargetForSelector官方文档

forwardingTargetForSelector

2.2 消息快速转发

GomuPerson.m

//- (void)sayNO{ NSLog(@"调用:%s",__func__); }

- (id)forwardingTargetForSelector:(SEL)aSelector
{
    if (aSelector == @selector(sayNO)) {
        NSLog(@"%s ----- %@",__func__,NSStringFromSelector(aSelector));
//: -- 这里如果不处理依然会崩溃,并且转发的接受者必须实现了sayNO
        return [GomuBoy alloc];
    }
    return [super forwardingTargetForSelector:aSelector];
}

GomuBoy.m
//: -- 实现方法
- (void)sayNO{ NSLog(@"调用:%s",__func__); }

//: -- 调用
GomuPerson *p = [GomuPerson alloc];
[p sayNO];

//: -- 打印
-[GomuPerson forwardingTargetForSelector:] ----- sayNO
调用:-[GomuBoy sayNO]
  • 我们成功的把GomuPerson中没有实现的方法sayNO,转发给毫无关系但是实现了sayNO方法的GomuBoy,有效的防止了方法未实现发送的崩溃

  • 当您只想将消息重定向另一个对象,并且比常规转发快一个数量级时,此功能很有用。

  • 快速速消息转发只能重定向消息的接受者

  • forwardingTargetForSelector可以写在本类或者父类或者它们的分类中,都能起到消息快速转发的作用

三、慢速消息转发methodSignatureForSelector

3.1 methodSignatureForSelector文档

methodSignatureForSelector

3.2 消息慢速转发

//: -- 单独使用此方法也会崩溃,必须配合forwardInvocation使用
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
{
    if (aSelector == @selector(sayNO)) {
        NSLog(@"%s ----- %@",__func__,NSStringFromSelector(aSelector));
//: -- 注意:这里返回nil,会报错`unrecognized selector sent to instance 0x100752600`
//: -- 返回签名
        return [NSMethodSignature signatureWithObjCTypes:"v@:"];
    }
    return [super methodSignatureForSelector:aSelector];
}

//: -- 事务处理,可以重定向当前方法的接收者(GomuBoy),也可以重定向方法(sayCode)
- (void)forwardInvocation:(NSInvocation *)anInvocation
{
    NSLog(@"%s ----- %@",__func__,NSStringFromSelector(anInvocation.selector));
    if (anInvocation.selector == @selector(sayNO)) {
//: -- 注意: 下面可以不实现,也不会崩溃,但是forwardInvocation方法必须实现
//: -- 下面2种处理方式二选一
//: -- 重定向方法 sayNO-> sayCode
//        anInvocation.selector = @selector(sayCode);
//: -- 重定向消息接受者 GomuPerson -> GomuBoy
        anInvocation.target = [GomuBoy alloc];
//: -- 调用
        [anInvocation invoke];
    }
}

//: -- 调用
GomuPerson *p = [GomuPerson alloc];
[p sayNO];

//: -- 打印
-[GomuPerson methodSignatureForSelector:] ----- sayNO
-[GomuPerson forwardInvocation:] ----- sayNO
调用:-[GomuBoy sayNO]
  • methodSignatureForSelector必须配合forwardInvocation使用,才能防止崩溃

  • unrecognized selector sent to instance错误是由methodSignatureForSelector方法返回nil的时候抛出的

  • forwardInvocation方法里面不处理事务也不会崩溃,只需要配合methodSignatureForSelector实现就能防止崩溃

  • 慢速消息转发不仅可以重新向消息的接受者,也可以重定向消息

  • 慢速消息转发的使用场景:可以对当前没有实现的方法,进行保存,想什么时候转发都可以,即维护一个不立即处理的消息

  • methodSignatureForSelectorforwardInvocation也可以写在本类或者父类或者它们的分类中,都能起到消息慢速转发的作用

四、resolveInstanceMethod、forwardingTargetForSelector、methodSignatureForSelector 比较

4.1 作用

  • resolveInstanceMethod 重定向消息对象 sayNO -> sayCode

  • forwardingTargetForSelector重定向消息接受者 GomuPerson -> GomuBoy

  • methodSignatureForSelector即可以重定向消息对象,也可以重定向消息接受者

4.2 使用场景

  • resolveInstanceMethod:立即调用,并且只需要重定向消息对象

  • forwardingTargetForSelector:立即调用,并且只需要重定向消息接收者

  • methodSignatureForSelector:可以任何时候调用,可以重定向消息对象,也可以重定向消息接收者

4.3 互斥性:只要有一个方法对消息进行了处理,就不会走后面流程

  • 如果resolveInstanceMethod对当前sel进行了处理,重定向了消息对象,就不会再调用forwardingTargetForSelectormethodSignatureForSelector

  • 如果resolveInstanceMethod没有处理,forwardingTargetForSelector重定向了消息接受者,methodSignatureForSelector就不会调用

五、通过方法调用验证上面流程图

//: -- 动态决议,只调方法不实现
+ (BOOL)resolveInstanceMethod:(SEL)sel
{
    if (sel == @selector(sayNO)) {
        NSLog(@"%s ----- %@",__func__,NSStringFromSelector(sel));
    }
    return [super resolveInstanceMethod:sel];
}

//: -- 消息开始转发,只调方法不实现
- (id)forwardingTargetForSelector:(SEL)aSelector
{
    if (aSelector == @selector(sayNO)) {
        NSLog(@"%s ----- %@",__func__,NSStringFromSelector(aSelector));
    }
    return [super forwardingTargetForSelector:aSelector];
}

//: -- 消息慢速转发,只调方法不实现
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
{
    if (aSelector == @selector(sayNO)) {
        NSLog(@"%s ----- %@",__func__,NSStringFromSelector(aSelector));
    }
    return [super methodSignatureForSelector:aSelector];
}

//: -- 事务处理,只调方法不实现
- (void)forwardInvocation:(NSInvocation *)anInvocation
{
    if (anInvocation.selector == @selector(sayNO)) {
        NSLog(@"%s ----- %@",__func__,NSStringFromSelector(anInvocation.selector));
    }
}

//: -- 三次机会都没处理,调用这里
- (void)doesNotRecognizeSelector:(SEL)aSelector
{
    if (aSelector == @selector(sayNO)) {
        NSLog(@"%s ----- %@",__func__,NSStringFromSelector(aSelector));
    }
    [super doesNotRecognizeSelector:aSelector];
}

打印:
+[GomuPerson resolveInstanceMethod:] ----- sayNO
-[GomuPerson forwardingTargetForSelector:] ----- sayNO
-[GomuPerson methodSignatureForSelector:] ----- sayNO
+[GomuPerson resolveInstanceMethod:] ----- sayNO
-[GomuPerson doesNotRecognizeSelector:] ----- sayNO
  • 再次验证了我们上面源码断点分析出的流程正确

六、拓展知识

6.1 监听objc底层消息发送,打印相关调用方法日志

//: -- 扩展:当前方法不在该类中,但是在其他地方有申明
//: -- 扩展声明系统没有提供的方法,才能直接调用
extern void instrumentObjcMessageSends(BOOL flag);

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        GomuPerson *p = [GomuPerson alloc];
//: -- 用该方法包裹研究对象,打印出来的就是对应的日志
        instrumentObjcMessageSends(YES);
        [p sayNO];
        instrumentObjcMessageSends(NO);
    }
    return 0;
}
  • 日志路径/tmp/msgSends/,如下图msgSends-11981
    日志路径.png
  • 打开日志,会发现我们前面流程图的调用顺序,整个消息转发机制调用方法(msgSend)的流程,如图


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