Runtime(五).消息转发

459 阅读6分钟

源码

从源码可知,如果动态解析条件不符合,会进入_objc_msgForward_impcache,这个方法搜不到,可猜测是汇编方法,搜 __objc_msgForward_impcache

搜到源码:

	STATIC_ENTRY __objc_msgForward_impcache

	MESSENGER_START
	nop
	MESSENGER_END_SLOW

	// No stret specialization.
	b	__objc_msgForward

	END_ENTRY __objc_msgForward_impcache

	
	ENTRY __objc_msgForward

	adrp	x17, __objc_forward_handler@PAGE
	ldr	x17, [x17, __objc_forward_handler@PAGEOFF]
	br	x17
	
	END_ENTRY __objc_msgForward

__objc_forward_handler,搜不到。猜测应该是C方法。搜_objc_forward_handler.搜到后无法再进行下去,因为到此后就不是开源的了。

国外小伙实现_objc_forward_handler的伪代码( __forwarding__.c ):

// 伪代码
int __forwarding__(void *frameStackPointer, int isStret) {
    id receiver = *(id *)frameStackPointer;
    SEL sel = *(SEL *)(frameStackPointer + 8);
    const char *selName = sel_getName(sel);
    Class receiverClass = object_getClass(receiver);

    // 调用 forwardingTargetForSelector:
    if (class_respondsToSelector(receiverClass, @selector(forwardingTargetForSelector:))) {
        id forwardingTarget = [receiver forwardingTargetForSelector:sel];
        if (forwardingTarget && forwardingTarget != receiver) {
            if (isStret == 1) {
                int ret;
                objc_msgSend_stret(&ret,forwardingTarget, sel, ...);
                return ret;
            }
            return objc_msgSend(forwardingTarget, sel, ...);
        }
    }

    // 僵尸对象
    const char *className = class_getName(receiverClass);
    const char *zombiePrefix = "_NSZombie_";
    size_t prefixLen = strlen(zombiePrefix); // 0xa
    if (strncmp(className, zombiePrefix, prefixLen) == 0) {
        CFLog(kCFLogLevelError,
              @"*** -[%s %s]: message sent to deallocated instance %p",
              className + prefixLen,
              selName,
              receiver);
        <breakpoint-interrupt>
    }

    // 调用 methodSignatureForSelector 获取方法签名后再调用 forwardInvocation
    if (class_respondsToSelector(receiverClass, @selector(methodSignatureForSelector:))) {
        NSMethodSignature *methodSignature = [receiver methodSignatureForSelector:sel];
        if (methodSignature) {
            BOOL signatureIsStret = [methodSignature _frameDescriptor]->returnArgInfo.flags.isStruct;
            if (signatureIsStret != isStret) {
                CFLog(kCFLogLevelWarning ,
                      @"*** NSForwarding: warning: method signature and compiler disagree on struct-return-edness of '%s'.  Signature thinks it does%s return a struct, and compiler thinks it does%s.",
                      selName,
                      signatureIsStret ? "" : not,
                      isStret ? "" : not);
            }
            if (class_respondsToSelector(receiverClass, @selector(forwardInvocation:))) {
                NSInvocation *invocation = [NSInvocation _invocationWithMethodSignature:methodSignature frame:frameStackPointer];

                [receiver forwardInvocation:invocation];

                void *returnValue = NULL;
                [invocation getReturnValue:&value];
                return returnValue;
            } else {
                CFLog(kCFLogLevelWarning ,
                      @"*** NSForwarding: warning: object %p of class '%s' does not implement forwardInvocation: -- dropping message",
                      receiver,
                      className);
                return 0;
            }
        }
    }

    SEL *registeredSel = sel_getUid(selName);

    // selector 是否已经在 Runtime 注册过
    if (sel != registeredSel) {
        CFLog(kCFLogLevelWarning ,
              @"*** NSForwarding: warning: selector (%p) for message '%s' does not match selector known to Objective C runtime (%p)-- abort",
              sel,
              selName,
              registeredSel);
    } // doesNotRecognizeSelector
    else if (class_respondsToSelector(receiverClass,@selector(doesNotRecognizeSelector:))) {
        [receiver doesNotRecognizeSelector:sel];
    }
    else {
        CFLog(kCFLogLevelWarning ,
              @"*** NSForwarding: warning: object %p of class '%s' does not implement doesNotRecognizeSelector: -- abort",
              receiver,
              className);
    }

    // The point of no return.
    kill(getpid(), 9);
}

上面代码精简成简单版后:

int __forwarding__(void *frameStackPointer, int isStret) {
    id receiver = *(id *)frameStackPointer;
    SEL sel = *(SEL *)(frameStackPointer + 8);
    const char *selName = sel_getName(sel);
    Class receiverClass = object_getClass(receiver);

    // 调用 forwardingTargetForSelector:
    if (class_respondsToSelector(receiverClass, @selector(forwardingTargetForSelector:))) {
        id forwardingTarget = [receiver forwardingTargetForSelector:sel];
        if (forwardingTarget && forwardingTarget != receiver) {
            return objc_msgSend(forwardingTarget, sel, ...);
        }
    }

    // 调用 methodSignatureForSelector 获取方法签名后再调用 forwardInvocation
    if (class_respondsToSelector(receiverClass, @selector(methodSignatureForSelector:))) {
        NSMethodSignature *methodSignature = [receiver methodSignatureForSelector:sel];
        if (methodSignature && class_respondsToSelector(receiverClass, @selector(forwardInvocation:))) {
            NSInvocation *invocation = [NSInvocation _invocationWithMethodSignature:methodSignature frame:frameStackPointer];

            [receiver forwardInvocation:invocation];

            void *returnValue = NULL;
            [invocation getReturnValue:&value];
            return returnValue;
        }
    }

    if (class_respondsToSelector(receiverClass,@selector(doesNotRecognizeSelector:))) {
        [receiver doesNotRecognizeSelector:sel];
    }

    // The point of no return.
    kill(getpid(), 9);
}
  • 首先会调用forwardingTargetForSelector方法,如果实现了该方法,且返回值不为空的话,继续objc_msgSend并把上面的返回值作为第一个参数传进去。
  • 如果不满足上面条件,会调用methodSignatureForSelector,获取方法签名。
  • 方法签名就是方法的返回值类型和参数类型。就是method_t里的types。
  • 如果符合条件并实现了forwardInvocation,先调用_invocationWithMethodSignature:frame:获取到invocation,invocation封装了方法调用,包括方法调用者,方法名,方法参数。
  • 接着会调用forwardInvocation,此时需要把传进去的invocation的target即方法调用者赋值(方法名,方法参数在方法签名时已经获取到)。
  • 如果不符合条件会调用doesNotRecognizeSelector,抛出经典错误unrecognize selector

Demo

demo1

//方法一
- (id)forwardingTargetForSelector:(SEL)aSelector
{
    if (aSelector == @selector(test)) {
        // objc_msgSend([[MJCat alloc] init], aSelector)
        return [[MJCat alloc] init];
    }
    return [super forwardingTargetForSelector:aSelector];
}

// 方法二
 //方法签名:返回值类型、参数类型
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
{
    if (aSelector == @selector(test)) {
        return [NSMethodSignature signatureWithObjCTypes:"v16@0:8"];
    }
    return [super methodSignatureForSelector:aSelector];
}

// NSInvocation封装了一个方法调用,包括:方法调用者、方法名、方法参数
//    anInvocation.target 方法调用者
//    anInvocation.selector 方法名
//    [anInvocation getArgument:NULL atIndex:0] 方法参数
- (void)forwardInvocation:(NSInvocation *)anInvocation
{
    anInvocation.target = [[MJCat alloc] init];
    [anInvocation invoke];

    // 或者[anInvocation invokeWithTarget:[[MJCat alloc] init]];
}

demo2

把MJClassInfo.h导入进来,不编译。main.m改成main.mm

#import <Foundation/Foundation.h>

@interface MJPerson : NSObject
- (int)test:(int)age;
-(void)a;
-(void)b;
-(void)c;


@end
#import "MJPerson.h"
#import <objc/runtime.h>
#import "MJCat.h"

@implementation MJPerson


- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
{

    if (aSelector == @selector(test:)) {
//        return [NSMethodSignature signatureWithObjCTypes:"v20@0:8i16"];
        return [NSMethodSignature signatureWithObjCTypes:"i@:i"];
//        return [[[MJCat alloc] init] methodSignatureForSelector:aSelector];
    }
    return [super methodSignatureForSelector:aSelector];
}

- (void)forwardInvocation:(NSInvocation *)anInvocation
{
    
    
    // 参数顺序:receiver、selector、other arguments
//    int age;
//    [anInvocation getArgument:&age atIndex:2];
//    NSLog(@"%d", age + 10);


    // anInvocation.target == [[MJCat alloc] init]
    // anInvocation.selector == test:
    // anInvocation的参数:15
    // [[[MJCat alloc] init] test:15]

    [anInvocation invokeWithTarget:[[MJCat alloc] init]];

    int ret;
    [anInvocation getReturnValue:&ret];

    NSLog(@"%d", ret);
}


-(void)a{};
-(void)b{};
-(void)c{};

@end

main.mm

#import <Foundation/Foundation.h>
#import "MJPerson.h"
#import "MJClassInfo.h"

// 消息转发:将消息转发给别人

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        MJPerson *person = [[MJPerson alloc] init];
        
        mj_objc_class *personCls1 = (__bridge mj_objc_class *)[MJPerson class];
        cache_t cache1 = personCls1->cache;
        
        [person a];
        [person b];
        [person c];
        
        [person test:15];
        
        
        mj_objc_class *personCls = (__bridge mj_objc_class *)[MJPerson class];
        cache_t cache = personCls->cache;
        bucket_t *buckets = cache._buckets;
        
        // 140734717512046  test
        // 140734152267456 init
        NSLog(@"init====%p",cache.imp(@selector(init)));
        NSLog(@"alloc====%p",cache.imp(@selector(alloc)));
        NSLog(@"test====%p",cache.imp(@selector(test:)));
        NSLog(@"其他====%p",cache.imp(@selector(forwardInvocation:)));
        
        for (int i= 0;i < sizeof(buckets);i ++) {
            bucket_t buckt = buckets[i];
            NSLog(@"%ld____%p",buckt._key,buckt._imp);
        }
        
        NSLog(@"哈哈哈");
        
    }
    return 0;
}

加入方法a,b,c是为了方便观察test方法是否会加入缓存。因为在[person a]打断点,会发现cache的长度是4,说明最开始缓存分配的长度是4.person初始化直接调test,后面会调其他方法,缓存长度重新分配,会被清空。就观察不到test是否会加入缓存。

在最后面打断点调试:

2020-07-24 13:11:03.271296+0800 Interview01-消息转发[59807:4433001] init====0x0
2020-07-24 13:11:03.271502+0800 Interview01-消息转发[59807:4433001] alloc====0x0
2020-07-24 13:11:03.271641+0800 Interview01-消息转发[59807:4433001] test====0x7fff6594bfdc
2020-07-24 13:11:03.271782+0800 Interview01-消息转发[59807:4433001] 其他====0x100001cb0
2020-07-24 13:11:03.272259+0800 Interview01-消息转发[59807:4433001] ==========================================
2020-07-24 13:11:03.273011+0800 Interview01-消息转发[59807:4433001] 0____0x0
2020-07-24 13:11:03.273135+0800 Interview01-消息转发[59807:4433001] 140734510489833____0x100001d90
2020-07-24 13:11:03.273265+0800 Interview01-消息转发[59807:4433001] 0____0x0
2020-07-24 13:11:03.273332+0800 Interview01-消息转发[59807:4433001] 140734152366115____0x100001cb0
2020-07-24 13:11:03.273395+0800 Interview01-消息转发[59807:4433001] 140734152365972____0x100001c10
2020-07-24 13:11:03.273458+0800 Interview01-消息转发[59807:4433001] 140734224094156____0x7fff6594bfdc
2020-07-24 13:11:03.273523+0800 Interview01-消息转发[59807:4433001] 140734717512046____0x7fff6594bfdc
2020-07-24 13:11:03.288887+0800 Interview01-消息转发[59807:4433001] 140734153143310____0x7fff659520a7
(lldb) p IMP(0x100001d90)
(IMP) $0 = 0x0000000100001d90 (Interview01-消息转发`-[MJPerson c] at MJPerson.m:53)
(lldb) p IMP(0x100001cb0)
(IMP) $1 = 0x0000000100001cb0 (Interview01-消息转发`-[MJPerson forwardInvocation:] at MJPerson.m:27)
(lldb) p IMP(0x100001c10)
(IMP) $2 = 0x0000000100001c10 (Interview01-消息转发`-[MJPerson methodSignatureForSelector:] at MJPerson.m:16)
(lldb) p IMP(0x7fff6594bfdc)
(IMP) $3 = 0x00007fff6594bfdc (libobjc.A.dylib`_objc_msgForward_impcache)
(lldb) p IMP(0x7fff659520a7)
(IMP) $4 = 0x00007fff659520a7 (libobjc.A.dylib`-[NSObject forwardingTargetForSelector:])
(lldb) 
  • 调用了forwardingTargetForSelector,methodSignatureForSelector,forwardInvocation,_objc_msgForward_impcache 这些方法
  • 转发完成后test也会作为key(哈希值为140734717512046)存入缓存,value为_objc_msgForward_impcache的地址值。可见源码中消息转发过程返回的IMP为_objc_msgForward_impcache的地址。
  • 调用test就相当于调用 forwardInvocation.
  • 学习forwardInvocation方法中NSInvocation的用法。参考:juejin.cn/post/685457…