0
点赞
收藏
分享

微信扫一扫

Objective-C 消息转发

鲤鱼打个滚 2021-09-22 阅读 59
日记本

前言

在《Objective-C 动态方法决议》一文中已经知道了动态方法协议的实现可以在resolveInstanceMethod方法中,但是resolveInstanceMethod方法之后就没有流程要走了嘛?其不然,立刻进行探索!

准备资料

  • objc4-818.2 源码
  • CF 源码
  • 反汇编工具Hopperida (ida最好是windows版本的)

消息转发

消息发送在经过动态方法决议仍然没有查找到正真的方法实现,此时动态方法决议抛出imp = forward_imp进入消息转发流程。转发流程分两步快速转发和慢速转发

日志辅助查看resolveInstanceMethod之后做了什么

通过lookUpImpOrForward --> log_and_fill_cache--> logMessageSend,进入logMessageSend看到源码的实现:

if (slowpath(objcMsgLogEnabled && implementer)) {
        bool cacheIt = logMessageSend(implementer->isMetaClass(), 
                                      cls->nameForLogging(),
                                      implementer->nameForLogging(), 
                                      sel);
        if (!cacheIt) return;
    }

logMessageSend的内部实现这里就不详细说明了,有兴趣的同学可以查看源码,里面主要是日志保存路径的设置以及一些相关内容的设定。通过保存的路径就可以查看输出的日志。

日志输出代码实现

extern void instrumentObjcMessageSends(BOOL flag);
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        XJLPerson * person = [XJLPerson alloc];
        instrumentObjcMessageSends(YES);
        [person sayLost];
        instrumentObjcMessageSends(NO);
    }
    return 0;
}

输出结果:

  • 在动态决议方法之后的消息转发流程有forwardingTargetForSelectormethodSignatureForSelector消息转发流程。

快速转发 --- forwardingTargetForSelector

forwardingTargetForSelector方法是不是感觉熟悉又陌生呢?那么我们通过文档看看他的描述是怎样子的。打开Xcodecommand + shift +0,然后全局搜索forwardingTargetForSelector,得到以下描述:


forwardingTargetForSelector含义是返回未识别消息重定向的对象,简单理解指定一个对象,让这个对象去接收这个消息。

实例探究快速转发

先定义两个类,分别是XJLPersonLGPerson,在XJLPerson中声明sayLost方法,但是不实现此方法,但是在LGPerson中实现sayLost方法。注意:XJLPersonLGPerson可以不是继承关系

@implementation XJLPerson

-(id)forwardingTargetForSelector:(SEL)aSelector{
    if (aSelector == @selector(sayLost)) {
        return [[LWTest alloc] init];
    }
    
    return  [super forwardingTargetForSelector:aSelector];
}
@end
@implementation LGPerson
-(void)sayHello{
    NSLog(@"---%s---",__func__);
}
@end
int main(int argc, char * argv[]) {

    @autoreleasepool {
        XJLPerson * person = [XJLPerson alloc];
        [person sayLost];
    }
    return 0;
}

打印结果:

2021-07-16 15:46:32.267031+0800 KCObjcBuild[33863:595901] ----[LGPerson sayLost]---

由打印结果可以得出,XJLPersonLGPerson两者是没有继承关系的,但是我们可以在forwardingTargetForSelector方法里面给XLJPerson指定实现的类(LGPerson)。此时sayLost方法仍然可以查询并实现,没有发生崩溃的情况。其实消息在查询过程中先去跟它关系近的类中去查找,最后没找到。于是系统把这个权限丢给开发者,你告诉我哪个对象和类能接收这个消息,这就是快速转发流程。
如果不给指定的类实现,快速转发也不行了,系统没有底线的给你进行慢速转发,这就有点不合理了吧。

慢速转发 -- methodSignatureForSelector

同理,我们先查看methodSignatureForSelector方法的官方描述,得到如下:


  • methodSignatureForSelector算是消息发送的最后一个流程了,在此方法到来之前已经允许开发者进行动态方法决议快速转发的处理,如果这两次机会都不珍惜的话,最后只能给你个报错信息咯。
  • methodSignatureForSelector的含义是返回一个NSMethodSignature对象,该对象包含由给定选择器标识的方法的描述。
  • methodSignatureForSelector一般搭配和forwardInvocation使用,如果methodSignatureForSelector方法返回的是一个nil就不会调用forwardInvocation

实例探究快速转发

@implementation XJLPerson
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
    return  [super methodSignatureForSelector:aSelector];
}

- (void)forwardInvocation:(NSInvocation *)anInvocation{
       NSLog(@"---%@---",anInvocation);
}
@end 

打印结果:

2021-07-16 16:12:00.760327+0800 KCObjcBuild[34202:602049] -[XJLPerson sayLost]: unrecognized selector sent to instance 0x100663ba0

此时methodSignatureForSelector的返回值是nil,慢速转发完成,直接奔溃!!

@implementation LWPerson
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
    if (aSelector == @selector(sayLost)) {
        return [NSMethodSignature signatureWithObjCTypes:"v@:"];
    }
    return  [super methodSignatureForSelector:aSelector];
}

- (void)forwardInvocation:(NSInvocation *)anInvocation{
        NSLog(@"---%@---%@",anInvocation.target,NSStringFromSelector(anInvocation.selector));
}
@end 

打印结果:

2021-07-16 16:16:42.540913+0800 KCObjcBuild[34317:604754] ---<XJLPerson: 0x10065d500>---sayLost---

如果methodSignatureForSelector的返回值是NSMethodSignature对象,则会调用forwardInvocation进行实物处理anInvocation保存了NSMethodSignature签名信息,还有目标方法的方法签名sel,以及方法的接收者。此时不会报奔溃信息,当然也可以处理anInvocaion事务。

- (void)forwardInvocation:(NSInvocation *)anInvocation{
    LGPerson* person = [LGPerson alloc];
    anInvocation.target = person;
    anInvocation.selector = @selector(sayNB);
    [anInvocation invoke];
}

打印结果:

2021-07-16 16:21:34.728551+0800 KCObjcBuild[34432:606907] -[LGPerson sayNB]
  • anInvocationtarget是[XJLPerson alloc],anInvocationselector@selector(sayNB)[anInvocation invoke]触发消息的调用。
  • forwardInvocation方法就像一个不能识别的消息的分发中心,它能够将一个消息翻译成另外一个消息,或者简单的"吃掉“某些消息。所以不处理也不会崩溃。

消息换发总结

  • 快速转发:通过forwardingTargetForSelector实现,如果此时有指定的对象去接收这个消息,就会走之指定对象的查找流程,如果返回是nil,进入慢速转发流程。
  • 慢速转发:通过methodSignatureForSelectorforwardInvocation共同实现,如果methodSignatureForSelector返回值是nil,慢速查找流程结束,如果返回值不为nil,forwardInvocation的事务处理不处理都不会崩溃
  • 可以在forwardInvocation方法中重新设定targetselector来进行处理。

消息转发流程图

反汇编探究

  • 方法崩溃之后会继续调用__forwarding_prep_0__方法。
  • __forwarding_prep_0__是属于coreFoundation系统库的。
  • __forwarding_prep_0__方法走完之后会进入doesNotRecognizeSelector方法。(下面反汇编会引出)

下个符号断点看看汇编的情况


下载CoreFoundation库以后的代码,在源码中全局搜索并无发现,说明这块内容苹果并没有对外提供。

Hopper或者IDA反汇编

Hopper反汇编的工具,我们通过反汇编CoreFoundation的可执行文件,去查找__forwarding_prep_0___CoreFoundation的可执行文件怎么获取的呢?可以在lldb调试中使用image list查看系统库加载,然后每个库都有对应的路径,根据路径找到coreFoundation如下:


拖入coreFoundation的执行文件到Hopper,并查询forwarding_prep_0_


发现我们无法查找__forwarding_prep_0___的位置,只是发现了__forwarding__方法!!
进入forwarding方法

  • 如果forwardingTargetForSelector方法没有实现,跳转loc_64ad7流程。
  • 如果forwardingTargetForSelector方法的返回值是nil,跳转 loc_64ad7流程。


    此时进入了消息的慢转发流程:
  • 如果methodSignatureForSelector没有实现直接跳转到loc_64e47流程。
  • 如果methodSignatureForSelector返回值等于nil跳转到loc_64eac流程。
  • 如果methodSignatureForSelector返回了签名信息的对象。

  • loc_64e47流程:直接报错跳转到loc_64eac流程。
  • loc_64eac流程:doesNotRecognizeSelector崩溃处理。

  • 返回签名信息,forwardInvocation根据签名信息处理相关事务。

总结

消息的转发探究就到这了,学习过程中收获满满哦。加油!

举报

相关推荐

0 条评论