OC底层探索(十)objc_msgSend 流程之方法的动态方法决议和消息的快速转发、慢速转发

前提

在前面两篇文章OC底层探索(八)objc_msgSend 流程之方法快速查找OC底层探索(九)objc_msgSend 流程之方法慢速查找中,分别分析了objc_msgSend的快速查找和慢速查找,在这两种都没找到方法实现的情况下,苹果给了两个建议

  • 动态方法决议:慢速查找流程未找到后,会执行一次动态方法决议
  • 消息转发:如果动态方法决议仍然没有找到实现,则进行消息转发

环境准备

新建Person类

Person.h

@interface Person : NSObject
@property (nonatomic, copy) NSString *lgName;
@property (nonatomic, strong) NSString *nickName;

- (void)sayNB;
- (void)sayMaster;
- (void)say666;
- (void)sayHello;

+ (void)sayNB;
+ (void)lgClassMethod;

@end

Person.m

@implementation Person
- (void)sayHello{
    NSLog(@"%s",__func__);
}

- (void)sayNB{
    NSLog(@"%s",__func__);
}
- (void)sayMaster{
    NSLog(@"%s",__func__);
}


+ (void)lgClassMethod{
    NSLog(@"%s",__func__);
}
@end

main.m文件

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // insert code here...
        
        Person *person = [Person alloc];
        //调用实例方法
        [person say666];
        //调用类方法
        [Person sayNB];

    }
    return 0;
}

动态方法决议【第一次机会】

在文章OC底层探索(九)objc_msgSend 流程之方法慢速查找中,我们介绍慢速查找的时候,没有找到imp,那么苹果就会给一次机会,挽救一下。—— 【第一次机会】

源码

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);
}

分析

  • 判断是否是元类
    · 如果是,执行实例方法的动态方法决议resolveInstanceMethod
    · 如果是元类,执行类方法的动态方法决议resolveClassMethod,如果在元类中没有找到或者为,则在元类实例方法的动态方法决议resolveInstanceMethod中查找,主要是因为类方法元类中是实例方法,所以还需要查找元类实例方法动态方法决议
  • 如果动态方法决议中,将其实现指向了其他方法,则继续查找指定的imp,即继续慢速查找lookUpImpOrForward流程

调用未实现的实例方法,查看打印

  • 在main.m文件中调用person的say666方法
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // insert code here...
        
        Person *person = [Person alloc];
        [person say666];
        [Person sayNB];

    }
    return 0;
}
  • 打印结果
    调用实例方法say666报的错
    在这里插入图片描述
    调用类方法sayNB报的错
    在这里插入图片描述

  • 分析
    由于在快速查找和慢速查找时,都没有找到say666实例方法sayNB类方法的实现,所以程序崩溃。

  • resolveInstanceMethod的源码

static void resolveInstanceMethod(id inst, SEL sel, Class cls)
{
    runtimeLock.assertUnlocked();
    ASSERT(cls->isRealized());
    SEL resolve_sel = @selector(resolveInstanceMethod:);

    // lookup 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));
        }
    }
}

实例方法

第一次补救

  • person.m文件中重写resolveInstanceMethod方法,先return父类的此方法。
+ (BOOL)resolveInstanceMethod:(SEL)sel{
    NSLog(@"%@ say666来了",NSStringFromSelector(sel));

    return [super resolveInstanceMethod:sel];
}

打印结果:
在这里插入图片描述

分析
· 在执行实例方法say666时,在快速、慢速查找都没有找到的情况下,就会执行到resolveInstanceMethod方法。但是我们查看打印,执行了两遍这个方法,第次执行是在慢速查找结束后执行的,那么第二次是在什么时候呢?此问题我们先记录一下。

  • 继续修改resolveInstanceMethod方法,将其返回一个固定的imp
+ (BOOL)resolveInstanceMethod:(SEL)sel{
    if (sel == @selector(say666)) {
        NSLog(@"%@ 来了", NSStringFromSelector(sel));
        //获取sayMaster方法的imp
        IMP imp = class_getMethodImplementation(self, @selector(sayMaster));
        //获取sayMaster的实例方法
        Method sayMethod  = class_getInstanceMethod(self, @selector(sayMaster));
        //获取sayMaster的丰富签名
        const char *type = method_getTypeEncoding(sayMethod);
        //将sel的实现指向sayMaster
        return class_addMethod(self, sel, imp, type);
    }
    
    return [super resolveInstanceMethod:sel];
}

打印结果:
在这里插入图片描述
分析:

· 在第动态方法决议时,返回sayMaster方法的imp,那么此时说明已经找到一个方法的IMP,直接返回imp

类方法

针对类方法,与实例方法类似,同样可以通过重写resolveClassMethod类方法来解决前文的崩溃问题,即在LGPerson类中重写该方法,并将sayNB类方法的实现指向类方法lgClassMethod

+ (BOOL)resolveClassMethod:(SEL)sel{
    
    if (sel == @selector(sayNB)) {
        NSLog(@"%@ 来了", NSStringFromSelector(sel));
        
        IMP imp = class_getMethodImplementation(objc_getMetaClass("Person"), @selector(lgClassMethod));
        Method lgClassMethod  = class_getInstanceMethod(objc_getMetaClass("Person"), @selector(lgClassMethod));
        const char *type = method_getTypeEncoding(lgClassMethod);
        return class_addMethod(objc_getMetaClass("Person"), sel, imp, type);
    }
    
    return [super resolveClassMethod:sel];
}

打印结果:
在这里插入图片描述
分析:
resolveClassMethod类方法的重写需要注意一点,传入的cls不再是,而是元类,可以通过objc_getMetaClass方法获取类的元类,原因是因为类方法元类中是实例方法

  • 在NSObject中实现resolveInstanceMethod,也可以解决类方法找不到问题

在源码中,如果是元类的话,会有下面这行代码,如果在没有找到类方法,那么就会在元类的对象方法解析方法中查找。

if (!lookUpImpOrNil(inst, sel, cls)) { //如果没有找到或者为空,在元类的对象方法解析方法中查找
            resolveInstanceMethod(inst, sel, cls);
        }

· 在isa走位图中,继承关系是:元类 -> 根元类 -> NSObject -> nil元类最终继承于NSObject,那么我们在NSObject中实现resolveInstanceMethod,那么类方法也会最终执行到NSObject中。

NSObject分类

@implementation NSObject (LG)

// 调用方法的时候 - 分类

+ (BOOL)resolveInstanceMethod:(SEL)sel{
    

    NSLog(@"%@ 来了",NSStringFromSelector(sel));
    if (sel == @selector(say666)) {
        NSLog(@"%@ 来了",NSStringFromSelector(sel));

        IMP imp           = class_getMethodImplementation(self, @selector(sayMaster));
        Method sayMMethod = class_getInstanceMethod(self, @selector(sayMaster));
        const char *type  = method_getTypeEncoding(sayMMethod);
        return class_addMethod(self, sel, imp, type);
    }
    else if (sel == @selector(sayNB)) {
        
        IMP imp           = class_getMethodImplementation(objc_getMetaClass("Person"), @selector(lgClassMethod));
        Method sayMMethod = class_getInstanceMethod(objc_getMetaClass("Person"), @selector(lgClassMethod));
        const char *type  = method_getTypeEncoding(sayMMethod);
        return class_addMethod(objc_getMetaClass("Person"), sel, imp, type);
    }
    return NO;
}
@end

在这里插入图片描述
分析:
· 打印结果中调用NSObjectresolveInstanceMethod方法,
· 但是也打印一些不相关的信息,这些信息说明了别的方法也会走到此方法中。

总结:

  • 实例方法动态方法决议时执行的是resolveInstanceMethod方法进行补救。
  • 类方法动态方法决议时执行的是resolveClassMethodresolveInstanceMethod方法进行补救。

消息转发

在慢速查找的流程中,我们了解到,如果快速+慢速没有找到方法实现,动态方法决议也不行,就使用消息转发,但是,我们找遍了源码也没有发现消息转发的相关源码,可以通过以下方式来了解,方法调用崩溃前都走了哪些方法。

流程图:
在这里插入图片描述

通过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 sayHello];
        instrumentObjcMessageSends(NO);
        NSLog(@"Hello, World!");
    }
    return 0;
}

  • 通过logMessageSend源码,了解到消息发送打印信息存储在/tmp/msgSends 目录,如下所示
    在这里插入图片描述

  • 运行代码,并前往/tmp/msgSends 目录,发现有msgSends开头的日志文件,打开发现在崩溃前,执行了以下方法

    · 两次动态方法决议resolveInstanceMethod方法
    · 两次消息快速转发forwardingTargetForSelector方法
    · 两次消息慢速转发methodSignatureForSelector + resolveInstanceMethod
    在这里插入图片描述

消息快速转发【第二次机会】

消息的快速转发是执行的forwardingTargetForSelector方法。

  • 慢速查找,以及动态方法决议没有找到实现时,进行消息转发,首先是进行快速消息转发,即走到forwardingTargetForSelector方法

  • 如果返回消息接收者,在消息接收者中还是没有找到,则进入另一个方法的查找流程

  • 如果返回nil,则进入慢速消息转发

- (id)forwardingTargetForSelector:(SEL)aSelector{
    NSLog(@"%s - %@",__func__,NSStringFromSelector(aSelector));

    // runtime + aSelector + addMethod + imp
    return [super forwardingTargetForSelector:aSelector];
}

消息慢速转发【第三次机会】

慢速转发

针对第次机会即快速转发中还是没有找到,则进入最后的一次挽救机会,即在Person中重写methodSignatureForSelector,如下所示

- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
    NSLog(@"%s - %@",__func__,NSStringFromSelector(aSelector));
    return [NSMethodSignature signatureWithObjCTypes:"v@:"];
}

- (void)forwardInvocation:(NSInvocation *)anInvocation{
    NSLog(@"%s - %@",__func__,anInvocation);
    // GM  sayHello - anInvocation - 漂流瓶 - anInvocation
    anInvocation.target = [LGStudent alloc];
    // anInvocation 保存 - 方法
    [anInvocation invoke];
}

打印结果:
在这里插入图片描述
分析:

  • methodSignatureForSelector方法中返回了一个方法类型
  • forwardInvocation,处理返回的方法类型,并处理invocation事务,修改invocationtarget[LGStudent alloc],调用 [anInvocation invoke] 触发 即LGPerson类的say666实例方法的调用会调用LGStudentalloc方法。

慢速转发中的动态方法决议

  • 我们在resolveInstanceMethodlookUpImpOrNil处打个断点,查看当前堆栈信息

  • 第一次执行动态方法决议时的堆栈是:
    _objc_msgSend_uncached ==> lookUpImpOrForward ==> resolveInstanceMethod
    在这里插入图片描述

  • 次执行动态方法决议时的堆栈是:
    methodSignatureForSelector > lookUpImpOrForward > resolveInstanceMethod
    在这里插入图片描述

  • 在堆栈信息中,第次执行动态方法决议时,是在执行消息慢速转发时执行的,并且是methodSignatureForSelector之后,forwardInvocation之前。

总结

OC方法调用就是消息发送,那么objc_msgSend流程分别是:

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值