iOS底层原理之消息转发

2022-01-14 14:45:20 浏览数 (3)

前言

IOS底层原理之动态方法决议中探究了动态方法决议。

在动态决议之后,通过日志辅助功能认识到forwardingTargetForSelector和

methodSignatureForSelector方法,也就是消息发送的最后一个流程消息转发。

准备工作

  • objc4-818.2 源码
  • CF 源码
  • 反汇编工具Hopper和ida

消息转发

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

快速转发

forwardingTargetForSelector

通过日志辅助功能认识到forwardingTargetForSelector,但是这个方法具体的含义是一无所知的,下面来认识下这个方法。打开Xcode,command shift 0,然后全局搜索forwardingTargetForSelector

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

实例探究快速转发

首先定义LWPerson类和LWTest类,然后在main函数中调用sayHello方法。

LWPerson没有实现sayHello方法,LWTest类实现sayHello方法,LWTest类和LWPerson类可以不是继承关系

代码语言:javascript复制
@implementation LWPerson

-(id)forwardingTargetForSelector:(SEL)aSelector{
    if (aSelector == @selector(sayHello)) {
        return [[LWTest alloc] init];
    }

    return  [super forwardingTargetForSelector:aSelector];
}
@end

(滑动显示更多)

代码语言:javascript复制
@implementation LWTest
-(void)sayHello{
    NSLog(@"---%s---",__func__);
}
@end

(滑动显示更多)

代码语言:javascript复制
int main(int argc, char * argv[]) {

    @autoreleasepool {
        LWPerson * p = [LWPerson alloc];
        [p sayHello];
    }
    return 0;
}

(滑动显示更多)

代码语言:javascript复制
2021-07-05 17:21:58.201416 0800 objc_msgSend[23131:807070] ----[LWTest sayHello]---

(滑动显示更多)

打印结果显示:LWTest类和LWPerson类没有任何关系,但是指定给LWTest类,仍然最后可以查询到,并且没有崩溃消息,其实消息在查询过程中先去跟它关系近的类中去查找,最后没找到。

于是系统把这个权限丢给开发者,你告诉我哪个对象和类能接收这个消息,破罐子破摔了,这我没想到的啊。

如果不给指定的类实现,快速转发也不行了,系统没有底线的给你进行慢速转发,就离谱

慢速转发

慢速转发methodSignatureForSelector也是消息查找的最后一个流程。给了动态方法决议,给了快速转发,在给你次慢速转发的机会。

俗话说事不过三,太过分了系统也忍不了,只能给你个崩溃耗子尾汁吧

methodSignatureForSelector的含义是返回一个NSMethodSignature对象,该对象包含由给定选择器标识的方法的描述。

methodSignatureForSelector一般搭配和forwardInvocation使用,

如果methodSignatureForSelector方法返回的是一个nil就不会调用forwardInvocation

代码语言:javascript复制
@implementation LWPerson
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
    return  [super methodSignatureForSelector:aSelector];
}

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

(滑动显示更多)

代码语言:javascript复制
2021-07-05 21:09:59.688392 0800 objc_msgSend[1121:21900] -[LWPerson sayHello]: 
unrecognized selector sent to instance 0x60000001c000

(滑动显示更多)

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

代码语言:javascript复制
@implementation LWPerson
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
    if (aSelector == @selector(sayHello)) {
        return [NSMethodSignature signatureWithObjCTypes:"v@:"];
    }
    return  [super methodSignatureForSelector:aSelector];
}

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

(滑动显示更多)

代码语言:javascript复制
2021-07-05 21:42:47.001759 0800 objc_msgSend[1629:44959] ---<LWPerson: 0x60000000c140>---sayHello

(滑动显示更多)

如果methodSignatureForSelector的返回值是NSMethodSignature对象,则会调用forwardInvocation进行实物处理。

anInvocation保存了NSMethodSignature签名信息,还有目标方法的方法签名sel,以及方法的接收者。

此时不会报奔溃信息,当然也可以处理anInvocaion事务。

代码语言:javascript复制
- (void)forwardInvocation:(NSInvocation *)anInvocation{
    LWTest * test = [LWTest alloc];
    anInvocation.target = test;
    anInvocation.selector = @selector(sayBeautiful);
    [anInvocation invoke];
}

(滑动显示更多)

代码语言:javascript复制
2021-07-05 21:52:29.117134 0800 objc_msgSend[1787:51943] ----[LWTest sayBeautiful]---

(滑动显示更多)

anInvocation的target是[LWTest alloc],anInvocation的selector是@selector(sayBeautiful),[anInvocation invoke]触发消息的调用。

forwardInvocation方法就像一个不能识别的消息的分发中心,它能够将一个消息翻译成另外一个消息,或者简单的"吃掉“某些消息。所以不处理也不会崩溃

消息换发总结

  • 快速转发:通过forwardingTargetForSelector实现,如果此时有指定的对象去接收这个消息,就会走之指定对象的查找流程,如果返回是nil,进入慢速转发流程
  • 慢速转发:通过methodSignatureForSelector和forwardInvocation共同实现,如果methodSignatureForSelector返回值是nil,慢速查找流程结束,如果有返回值forwardInvocation的事务处理不处理都不会崩溃

消息转发流程图

反汇编

方法调用奔溃,堆栈信息显示从__forwarding_prep_0___ 往下调用最后又调用了doesNotRecognizeSelector。具体的流程探究下

__forwarding_prep_0___是属于CoreFoundation系统库的

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

Hopper或者ida探索

Hopper反汇编的工具,我们通过反汇编CoreFoundation的可执行文件,

去查找__forwarding_prep_0___,CoreFoundation的可执行文件怎么获取

image list可以获取所有的镜像文件列表,查找到CoreFoundation的文件路径

全局搜索__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 人点赞