十、消息流程—动态方法决议 & 消息转发

主要内容:objc_msgSend快速查找慢速查找,都没有找到方法时,苹果给了挽救的机会
一、动态方法决议
1.对象方法
2.类方法
二、消息转发
1.快速消息转发
2.慢速消息转发

一、动态方法决议

动态方法决议:慢速查找流程未找到方法,会给一次机会,重新查询方法
在objc_msgSend流程—慢速查找 中慢速查找流程未找到方法实现时,会执行一次动态方法决议``resolveMethod_locked方法,源码如下:

static NEVER_INLINE IMP
resolveMethod_locked(id inst, SEL sel, Class cls, int behavior)
{
    runtimeLock.assertLocked();
    ASSERT(cls->isRealized());

    runtimeLock.unlock();
    // 对象 -- 类
    if (! cls->isMetaClass()) { // 类不是元类,调用对象方法决议
        // try [cls resolveInstanceMethod:sel]
        resolveInstanceMethod(inst, sel, cls);
    } 
    else { // 如果是元类,调用类方法决议
        // try [nonMetaClass resolveClassMethod:sel]
        // and [cls resolveInstanceMethod:sel]
        resolveClassMethod(inst, sel, cls);
        // 为什么要再走一次对象方法决议?-- 类方法在元类中是以对象方法存在的,所以还是需要查询元类中对象方法的动态方法决议
        if (!lookUpImpOrNil(inst, sel, cls)) { // 如果没有找到或者为空,在元类的对象方法决议中查找
            resolveInstanceMethod(inst, sel, cls);
        }
    }

    // chances are that calling the resolver have populated the cache
    // so attempt using it
    // 如果方法解析中将其他实现指向其他方法,则继续走方法查找流程
    return lookUpImpOrForward(inst, sel, cls, behavior | LOOKUP_CACHE);
}

流程图


image.png
1.对象方法

对象方法快速查找 -> 慢速查找没有找到的情况下,会走到 resolveInstanceMethod 方法,源码如下:

static void resolveInstanceMethod(id inst, SEL sel, Class cls)
{
    runtimeLock.assertUnlocked();
    ASSERT(cls->isRealized());
    SEL resolve_sel = @selector(resolveInstanceMethod:);
    // look的是 resolveInstanceMethod --相当于是发送消息前的容错处理
    if (!lookUpImpOrNil(cls, resolve_sel, cls->ISA())) {
        // Resolver not implemented.
        return;
    }

    BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
    bool resolved = msg(cls, resolve_sel, sel);

    // Cache the result (good or bad) so the resolver doesn't fire next time.
    // +resolveInstanceMethod adds to self a.k.a. cls
    IMP imp = lookUpImpOrNil(inst, sel, cls);

    if (resolved  &&  PrintResolving) {
        if (imp) {
            _objc_inform("RESOLVE: method %c[%s %s] "
                         "dynamically resolved to %p", 
                         cls->isMetaClass() ? '+' : '-', 
                         cls->nameForLogging(), sel_getName(sel), imp);
        }
        else {
            // Method resolver didn't add anything?
            _objc_inform("RESOLVE: +[%s resolveInstanceMethod:%s] returned YES"
                         ", but no new implementation of %c[%s %s] was found",
                         cls->nameForLogging(), sel_getName(sel), 
                         cls->isMetaClass() ? '+' : '-', 
                         cls->nameForLogging(), sel_getName(sel));
        }
    }
}

主要步骤

  • 发送resolveInstanceMethod消息前,需要查找cls类中是否有该方法的实现,即通过lookUpImpOrNil方法又会进入lookUpImpOrForward慢速查找流程查找resolveInstanceMethod方法
    • 如果没有,则直接返回
    • 如果有,则发送resolveInstanceMethod消息
  • 再次慢速查找实例方法的实现,即通过lookUpImpOrNil方法又会进入lookUpImpOrForward慢速查找流程查找实例方法

代码

  • main
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        LGPerson *person = [LGPerson alloc];
        [person ins_say666];
    }
    return 0;
}
  • LGPerson
@interface LGPerson : NSObject
- (void) ins_say666;
- (void)ins_sayMaster;
+ (void) cls_sayHappy;
@end

@implementation LGPerson

- (void)ins_sayMaster{
    NSLog(@"ins_sayMaster - %s", __func__);
}
+ (void) cls_sayHappy{
    NSLog(@"cls_sayHappy");
}
+ (BOOL)resolveInstanceMethod:(SEL)sel{
    NSLog(@"%@ 来了",NSStringFromSelector(sel));
    return [super resolveInstanceMethod:sel];
}

打印结果:

image.png

执行到了LGPerson中的resolveInstanceMethod方法,还执行了两次,而且程序还是崩溃了,通过堆栈信息可以看出

image.png
  • 【第一次动态决议】第一次的“来了”是在查找ins_say666方法时会进入动态方法决议
  • 【第二次动态决议】第二次“来了”是在慢速转发流程中调用了>CoreFoundation框架中的NSObject(NSObject) methodSignatureForSelector:后,会再次进入动态决议

继续修改

+ (BOOL)resolveInstanceMethod:(SEL)sel{
    NSLog(@"%@ 来了",NSStringFromSelector(sel));

    if (sel == @selector(ins_say666)) {
        //获取ins_sayMaster方法的imp
        IMP imp = class_getMethodImplementation(self, @selector(ins_sayMaster));
        //获取ins_sayMaster的方法签名
        Method sayMMethod = class_getInstanceMethod(self, @selector(ins_sayMaster));
        const char *type  = method_getTypeEncoding(sayMMethod);
        //将sel的实现指向ins_sayMaster
        return class_addMethod(self, sel, imp, type);
    }
    return [super resolveInstanceMethod:sel];
}

打印结果:


image.png

方法重定向后,不再崩溃。

2.类方法

对类方法,通过重写resolveClassMethod类方法来解决方法未找到的崩溃问题,即在LGPerson类中重写该方法,并将cls_sayNB类方法的实现指向类方法cls_sayHappy

+ (BOOL)resolveClassMethod:(SEL)sel {
    NSLog(@"%@ 来了",NSStringFromSelector(sel));

    if (sel == @selector(cls_sayNB)) {
        IMP imp = class_getMethodImplementation(objc_getMetaClass("LGPerson"), @selector(cls_sayHappy));
        Method sayMHappy = class_getInstanceMethod(objc_getMetaClass("LGPerson"), @selector(cls_sayHappy));
        const char *type  = method_getTypeEncoding(sayMHappy);
        return class_addMethod(objc_getMetaClass("LGPerson"), sel, imp, type);
    }
    return [super resolveClassMethod:sel];
}

resolveClassMethod类方法的重写需要注意一点,传入的cls不再是,而是元类,可以通过objc_getMetaClass方法获取类的元类,原因是因为类方法元类中是实例方法

优化
上面的这种方式是单独在每个类中重写,有没有更好的?其实通过方法慢速查找流程可以发现查找路径有两条

  • 实例方法:类 -> 父类 -> 根类 -> nil
  • 类方法:元类 -> 根元类 -> 根类 -> nil

共同点是如果前面没有找到,都会来到 根类即 NSObject中查找。所以我们可以通过 NSObject添加分类的方式来实现统一处理。类方法在查找到元类和根类时,实际查找的也是对象方法,所以 对象方法和类方法都放在resolveInstanceMethod 中处理

+ (BOOL)resolveInstanceMethod:(SEL)sel{
    NSLog(@"%@ 来了",NSStringFromSelector(sel));

    if (sel == @selector(ins_say666)) {
        IMP imp = class_getMethodImplementation(self, @selector(ins_sayMaster));
        Method sayMMaster = class_getInstanceMethod(self, @selector(ins_sayMaster));
        const char *type  = method_getTypeEncoding(sayMMaster);
        return class_addMethod(self, sel, imp, type);
    } else if (sel == @selector(cls_sayNB)) {
        IMP imp = class_getMethodImplementation(objc_getMetaClass("LGPerson"), @selector(cls_sayHappy));
        Method sayMHappy = class_getInstanceMethod(objc_getMetaClass("LGPerson"), @selector(cls_sayHappy));
        const char *type  = method_getTypeEncoding(sayMHappy);
        return class_addMethod(objc_getMetaClass("LGPerson"), sel, imp, type);
    }
    return NO;
}

上面这种写法还是会有其他的问题,比如系统方法也会被更改,针对这一点,是可以优化的,即我们可以针对自定义类中方法统一方法名的前缀,根据前缀来判断是否是自定义方法,然后统一处理自定义方法,例如可以在崩溃前pop到首页,主要是用于app线上防崩溃的处理,提升用户的体验。也可以收集崩溃信息传到后台,再进行相应处理。

二、消息转发

消息转发:如果动态方法决议仍然没有找到实现,则进行消息转发
但我们找遍了源码也没有发现消息转发的相关源码,可以通过instrumentObjcMessageSends 方式打印发送消息日志

  • 通过lookUpImpOrForward -> log_and_fill_cache -> logMessageSend,在logMessageSend源码下方找到instrumentObjcMessageSends的源码实现,所以,在main中调用instrumentObjcMessageSends打印方法调用的日志信息,有以下两点准备工作
    • 1、打开 objcMsgLogEnabled开关,即调用instrumentObjcMessageSends方法时,传入YES
    • 2、在main中通过extern 声明instrumentObjcMessageSends方法
extern void  instrumentObjcMessageSends(BOOL flag);

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        LGPerson *person = [LGPerson alloc];
        instrumentObjcMessageSends(YES);
        [person ins_say666];
        instrumentObjcMessageSends(NO);
    }
    return 0;
}
  • 运行代码,前往 /tmp/msgSends 目录,发现有 msgSends 开头的日志文件,打开日志文件会发现:
    image.png
1.快速消息转发
// 快速转发
- (id)forwardingTargetForSelector:(SEL)aSelector{
    NSLog(@"%s - %@",__func__,NSStringFromSelector(aSelector));
//    return [super forwardingTargetForSelector:aSelector];
//将消息的接收者指定为LGTeacher,在LGTeacher中查找ins_say666的实现
    return [LGTeacher alloc];
}

打印结果:

image.png
2.慢速消息转发

有快速一般就有慢速,以下是慢速转发流程
如果forwardingTargetForSelector也没有找到,那就找
methodSignatureForSelector 方法,使用时会搭配 forwardInvocation, 就会实现慢速转发流程

// 慢速转发流程
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
    NSLog(@"%s - %@",__func__,NSStringFromSelector(aSelector));
    // 返回方法签名
    return [NSMethodSignature signatureWithObjCTypes:"v@:"];
}

- (void)forwardInvocation:(NSInvocation *)anInvocation{
    NSLog(@"%s - %@",__func__,anInvocation);
    anInvocation.target = [LGTeacher alloc];
    [anInvocation invoke];
}

打印结果

image.png
  • anInvocation 里存的信息:消息接收者、消息名称、消息签名
  • forwardInvocation方法中,不修改anInvocation也不会崩溃,是因为 GM 认为就是不处理这个方法了,那就谁愿意处理谁处理,把这个方法流出去,像漂流瓶一样
  • 慢速快速区别快速直接指定一个对象就OK了,慢速target可以改,sel 也可以改,可以保存之后想什么时候invoke就什么时候invoke, 可以给一个统一的全局的方法 。慢速转发流程,拥有的 权力更大 , 更加灵活

总结
消息流程:快速查找 -> 慢速查找 -> 动态方法决议 -> 消息转发(快速转发 + 慢速转发)

消息转发流程图.png

你可能感兴趣的:(十、消息流程—动态方法决议 & 消息转发)