iOS

iOS-动态方法决议 & 消息转发

2021-07-06  本文已影响0人  Summit_yp

iOS-慢速方法查找iOS-快速方法查找中我们分别提到了objc_msgSend的快速查找和慢速查找,如果经历这两步仍未找到该方法的imp会怎么样呢?
Apple给了我们两次补救的机会,
动态方法决议:慢速查找流程未找到后,会执行一次动态方法决议
消息转发:如果动态方法决议仍然没有找到实现,则进行消息转发
如果这两次机会都没有做任何操作,就会报我们日常开发中常见的方法未实现的崩溃报错——unrecognized selector sent to
我们来看看是怎么从慢速查找进入到第一个机会——动态方法决议的吧。

动态方法决议入口
康康该方法的源码
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);
}

先看看对象方法的动态决议源码

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

    if (!lookUpImpOrNilTryCache(cls, resolve_sel, cls->ISA(/*authenticated*/true))) {//查找resolveInstanceMethod是否实现,其实源码中NSObject实现了该方法。
        // Resolver not implemented.
        return;
    }
   //消息转发,执行resolveInstanceMethod方法
    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 = lookUpImpOrNilTryCache(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));
        }
    }
}

我们来代码验证一下,给LGPerson.h添加如下代码

@interface LGPerson : NSObject

+ (void)say1;//只声明,未实现
+ (void)say4;

- (void)say2;
- (void)say3;//只声明,未实现
@end

LGPerson.m添加如下代码

@implementation LGPerson

//- (void)say1
//{
//  NSLog(@"%s",__func__);
//}
- (void)say2
{
  NSLog(@"%s",__func__);
}
//- (void)say3
//{
//  NSLog(@"%s",__func__);
//}
+ (void)say4
{
  NSLog(@"%s",__func__);
}


+ (BOOL)resolveInstanceMethod:(SEL)sel{
    if (sel == @selector(say3)) {
        NSLog(@"%@ 来了", NSStringFromSelector(sel));
    }
    return [super resolveInstanceMethod:sel];
}
@end

main中调用say3,执行结果如下:

image.png
可以看到resolveInstanceMethod执行了两次,why?
-第一次的“来了”是在查找say3方法时会进入动态方法决议
-第二次“来了”是在慢速转发流程中调用了CoreFoundation框架中的NSObject(NSObject) methodSignatureForSelector:后,会再次进入动态决议
可通过lldbbt命令看看堆栈信息验证
image.png

如果想抓住这个防止崩溃的机会,可以修改resolveInstanceMethod代码如下

+ (BOOL)resolveInstanceMethod:(SEL)sel{
    if (sel == @selector(say3)) {
        NSLog(@"%@ 来了", NSStringFromSelector(sel));
        //获取say2方法的imp
        IMP imp = class_getMethodImplementation(self, @selector(say2));
        //获取say2的实例方法
        Method sayMethod  = class_getInstanceMethod(self, @selector(say2));
        //获取say2的签名
        const char *type = method_getTypeEncoding(sayMethod);
        //将sel的实现指向say2
        return class_addMethod(self, sel, imp, type);
    }
//
    return [super resolveInstanceMethod:sel];
}
上一篇下一篇

猜你喜欢

热点阅读