OC底层原理12 - 消息转发

2021-02-01  本文已影响0人  卡布奇诺_95d2

消息转发

经过了快速查找,慢速查找,动态方法解析之后,仍然没有找到SEL对应的IMP,此时我们在抛出异常信息的信息的时候,查看一下当前堆栈情况

frame #9: 0x00000001002e8c7e libobjc.A.dylib`objc_exception_throw(obj="-[HQPerson say666]: unrecognized selector sent to instance 0x100665a40") at objc-exception.mm:591:5
frame #10: 0x00007fff314de936 CoreFoundation`-[NSObject(NSObject) doesNotRecognizeSelector:] + 132
frame #11: 0x00007fff313c3ec0 CoreFoundation`___forwarding___ + 1427
frame #12: 0x00007fff313c3898 CoreFoundation`__forwarding_prep_0___ + 120
frame #13: 0x0000000100003b1e HQObjc`main(argc=1, argv=0x00007ffeefbff4f0) at main.m:154:9 [opt]
frame #14: 0x00007fff6b547cc9 libdyld.dylib`start + 1

从上面的堆栈信息中可以看出,在doesNotRecognizeSelector函数(该函数为抛出未找到selector的异常信息)之前,有两步CoreFoundation的操作,但___forwarding_____forwarding_prep_0___.CoreFoundation是不开源的,那如何分析这两个操作呢?这可以通过使用hopper工具进行反编译查看这两个函数的流程.本章忽略如何使用hopper工具.直接给出结果:

快速消息转发

forwardingTargetForSelector:当进行快速查找,慢速查找,动态方法解析之后,仍然没有查到SEL对应的IMP,这个时候,苹果给我们提供了第二次挽救的机会,即快速消息转发.

快速消息转发的中心思想是,既然在当前消息接收者中找不到SEL,那通过forwardingTargetForSelector函数,将当前的消息接收者可以转发给其它能处理该消息的类.

注意:forwardingTargetForSelector方法分为实例方法类方法,这两个方法在NSObject中都有实现,只是返回值为nil.

接下来尝试在第二次挽救机会中对HQPerosn的实例方法say666进行挽救.

@interface HQHuman : NSObject
-(void)say666;
@end
@implementation HQHuman
-(void)say666{
    NSLog(@"%s", __func__);
}
@end
- (id)forwardingTargetForSelector:(SEL)aSelector{
    if(aSelector == NSSelectorFromString(@"say666")){
        return [HQHuman alloc];
    }
    return nil;
}
2021-02-01 15:33:18.311088+0800 HQObjc[37844:2499110] Hello, World!
2021-02-01 15:33:21.403814+0800 HQObjc[37844:2499110] -[HQHuman say666]
2021-02-01 15:33:22.842117+0800 HQObjc[37844:2499110] end~
Program ended with exit code: 0

由结果可以看到,HQPerson类虽然没有实现say666的实例方法,但经过快速消息转发后,将say666消息接收者修改成HQHuman了.最终由HQHuman完成消息的执行.

慢速消息转发

要了解慢速消息转发,需要先了解NSInvocation.

NSInvocation命令模式的一种传统实现,它把一个目标、一个选择器、一个方法签名和所有的参数都塞进一个对象里,这个对象可以先存储起来,以备将来调用.

NSInvocation还包含一个方法签名(NSMethodSignature),它封装了一个方法的返回类型参数类型,记住它不包括方法名称,只有返回类型和参数类型。可以通过以下方法,手动的创建一个方法签名:

NSMethodSignature *sig = [NSMethodSignature signatureWithObjCTypes:"v@:"];

最后,NSInvocation还包含了所有的参数.

回到慢速消息转发.

当经过第二次挽救时,仍然未找到该SEL对应的IMP,此时苹果最后给了一次机会.即先调用methodSignatureForSelector,查看当前是否有返回方法签名.
若当前未返回方法签名,即返回nil,则报错.
若返回的方法签名不为nil,则创建一个NSInvocation,并传递给forwardInvocation.在前面对NSInvocation的描述中知道,有了NSInvocation对象,就能完成消息的执行.

注意:methodSignatureForSelector方法和forwardInvocation方法分为实例方法类方法,这两个方法在NSObject中都有实现.

接下来尝试在最后一次挽救机会中对HQPerosn的实例方法say666进行挽救.

- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
    return [NSMethodSignature signatureWithObjCTypes:"v@:"];
}
- (void)forwardInvocation:(NSInvocation *)anInvocation{
    [anInvocation invokeWithTarget:[[HQHuman alloc] init]];
}
2021-02-01 16:29:06.990005+0800 HQObjc[38332:2540396] Hello, World!
2021-02-01 16:29:12.188971+0800 HQObjc[38332:2540396] -[HQHuman say666]
2021-02-01 16:29:13.685739+0800 HQObjc[38332:2540396] end~
Program ended with exit code: 0

由结果同样可以看到,HQPerson类虽然没有实现say666的实例方法,但经过慢速消息转发后,将say666消息接收者修改成HQHuman了.最终由HQHuman完成消息的执行.

上一篇 下一篇

猜你喜欢

热点阅读