美文网首页
objc_msgSend方法查找的理解

objc_msgSend方法查找的理解

作者: 灰溜溜的小王子 | 来源:发表于2020-09-25 21:44 被阅读0次

前言:在上文objc_msgSend流程分析中方法查询到lookUpImpOrForward,其中对方法进行循环向上查找看其父类中是否存在,如果在父类对方法缓存列表中找到就返回Imp指针调用相应方法,如果没有怎么办?
咱在接下来对方法中一块探个究竟!

一.慢速查找流程

  • 如果找到方法,返回impgoto done,并保存方法到调用此方法到类到方法缓存列表,代码如下:
if (meth) {
            imp = meth->imp;
            goto done;
        }
...省略部分代码
log_and_fill_cache(cls, imp, sel, inst, curClass);
runtimeLock.unlock();
  • 遍历上层的父类还是没有查找到方法给imp赋值imp = forward_imp,并跳出循环,下一步执行方法如下:
if (slowpath(behavior & LOOKUP_RESOLVER)) {
        behavior ^= LOOKUP_RESOLVER;
        return resolveMethod_locked(inst, sel, cls, behavior);
    }

可见lookUpImpOrForward多次调用,if判断里只执行一次判断这就让resolveMethod_locked方法显得尤为重要(逻辑运算符计算)

二.动态方法解析(也叫决议)

1. resolveMethod_locked具体干了什么?
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);
}

这里主要有两个分支,主要是对cls做个是否元类的判断::

  • 不是元类,意味着调用的是实例方法,那么执行resolveInstanceMethod函数
  • 是元类,说明调用的是类方法,执行resolveClassMethod函数,之后如果依然没找到IMP,则再去执行resolveInstanceMethod函数;
1.1 resolveInstanceMethod 实例方法解析
static void resolveInstanceMethod(id inst, SEL sel, Class cls)
{
    runtimeLock.assertUnlocked();
    ASSERT(cls->isRealized());
    SEL resolve_sel = @selector(resolveInstanceMethod:);

    if (!lookUpImpOrNil(cls, resolve_sel, cls->ISA())) {
        // Resolver not implemented.
        // 如果你没有实现类方法 +(BOOL)resolveInstanceMethod:(SEL)sel
        // NSObject也有实现,所以一般不会走这里
        // 注意这里传入的第一个参数是:cls->ISA(),也就是元类
        return;
    }

    BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;// 调用类方法: +(BOOL)resolveInstanceMethod:(SEL)sel
    bool resolved = msg(cls, resolve_sel, sel);// 再找一次imp(这次是sel,而不是resolveInstanceMethod)

    // 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);
   // 检测是否有sel对应的IMP。假如你在+(BOOL)resolveInstanceMethod:(SEL)sel中添加了sel的函数地址IMP,此时再次去查找这个IMP就能找到。

    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));
        }
    }
}
  • 检查类方法是否有方法实现:SEL resolve_sel = @selector(resolveInstanceMethod:);
  • lookUpImpOrNil 这里的lookUpImpOrForward中的resolver为NO,所以只会在本类和父 类中查找,并不会动态方法解析。
  • 向本类发送SEL_resolveInstanceMethod消息,resolved = msg(cls, resolve_sel, sel)
  • 再次查找当前实例方法imp,找到就填充缓存,找不到就返回IMP imp = lookUpImpOrNil(inst, sel, cls);
  • 结束动态方法解析,回到lookUpImpOrForward方法将triedResolver置否并goto retry重新查找缓存方法列表

resolveInstanceMethod函数先后调用了两次lookUpImpOrNil:

  • 容错判断if (!lookUpImpOrNil(cls, resolve_sel, cls->ISA())) ,是为了看当前类以及父类、元类等有没有实现resolveInstanceMethod:,如果都没有就没必要继续往下走了。(NSObject里面是默认有这个方法的)

  • 如果实现了,就通过objc_msgSend,向当前cls发送消息,也就是调用resolveInstanceMethod:这个已经实现的方法,在这个方法里,我们已经手动给sel添加了一个imp

  • 然后再通过lookUpImpOrNil检查一遍,拿到我们添加的imp

  • 最后返回到lookUpImpOrForward方法,重新循环去找,最终返回imp

  • 注意到这两次调用中,resolver都是NO,因此在其调用lookUpImpOrForward时不会触发 消息的解析,仅仅是从“类、父类、...、根类”的缓存中和方法列表中找IMP,没找到会触发 消息转发

1.2 resolveClassMethod 类方法解析

跟实例方法相比,类方法稍微复杂了一点

// try [nonMetaClass resolveClassMethod:sel]
        // and [cls resolveInstanceMethod:sel]
        _class_resolveClassMethod(cls, sel, inst);
        if (!lookUpImpOrNil(cls, sel, inst, 
                            NO/*initialize*/, YES/*cache*/, NO/*resolver*/)) 
        {
            _class_resolveInstanceMethod(cls, sel, inst);
        }

_class_resolveClassMethod进入resolveClassMethod

static void resolveClassMethod(id inst, SEL sel, Class cls)
{
    runtimeLock.assertUnlocked();
    ASSERT(cls->isRealized());
    ASSERT(cls->isMetaClass());

    if (!lookUpImpOrNil(inst, @selector(resolveClassMethod:), cls)) {
        // Resolver not implemented.
        return;
    }

    Class nonmeta;
    {
        mutex_locker_t lock(runtimeLock);
        nonmeta = getMaybeUnrealizedNonMetaClass(cls, inst);
        // +initialize path should have realized nonmeta already
        if (!nonmeta->isRealized()) {
            _objc_fatal("nonmeta class %s (%p) unexpectedly not realized",
                        nonmeta->nameForLogging(), nonmeta);
        }
    }
    BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
    bool resolved = msg(nonmeta, @selector(resolveClassMethod:), sel);

    // Cache the result (good or bad) so the resolver doesn't fire next time.
    // +resolveClassMethod adds to self->ISA() 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 resolveClassMethod:%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));
        }
    }
}

在经过resolveClassMethod的处理之后,如果依然没有找到类方法的IMP,就会再次执行resolveInstanceMethod函数!不同于实例方法的是,此时的cls是元类,因此msg(cls, SEL_resolveInstanceMethod, sel);即是向元类内部发送resolveInstanceMethod:消息,也就意味着是根类调用resolveInstanceMethod:方法(这次只能在根类的分类中补救了),同时缓存查找类方法的IMP仅发生在根元类和根类中,而方法列表中查找类方法的IMP则分别在“元类、元类的父类、...、根元类、根类”中进行。
当我们调用一个类方法时,如果在类中没有实现,同时在resolveClassMethod中也没有处理,那么最终会调用根类(NSObject)的同名实例方法。

三.消息转发

1.3.举个例子

类PHFather中一个实例方法和类方法,且在.m中没有实现



运行结果如下图:

什么鬼?但是从堆栈信息中可以看到有forwarding这个调用函数
方法的调用经过了查找、解析,如果还是没有找到IMP,就会来到消息转发流程。它的入口在lookUpImpOrForward函数靠后的位置
https://juejin.im/post/6844904063524405255

https://juejin.im/post/6844904126887772173

相关文章

网友评论

      本文标题:objc_msgSend方法查找的理解

      本文链接:https://www.haomeiwen.com/subject/ljpnyktx.html