十三、objc_msgSend(方法的查找流程二)

118 阅读5分钟

本文由快学吧个人写作,以任何形式转载请表明原文出处

一、资料准备

objc4-818.2

对应mac的版本是11.1。可根据自己的系统版本挑选可以进行调试的源码。

二、思路

  1. 如果方法没有在缓存中存在,那么objc_msgSend是如何找到方法的实现的。
  2. 通过汇编找到对应的代码。
  3. 查看源码的实现

三、创建代码

  1. 在818.2中创建一个类(我创建的是JDPerson),继承于NSObject。并且定义一个实例方法。然后在main.m中创建实例,并调用这个实例方法。并在调用实例方法这一行打断点。

图片.png

图片.png

  1. 打开汇编

图片.png

  1. 运行代码

四、如何找到另一种方法查找流程

  1. 运行代码后,会进入汇编,在汇编中找到objc_msgSend那行,并打上断点,然后跳到objc_msgSend的断点处。

图片.png

  1. 进入objc_msgSend。

图片.png

  1. 在汇编的最后面会发现_objc_msgSend_uncached。在这里打上断点,然后执行到这里。然后按住control再点stepInto。

图片.png

  1. 发现调用的方法是lookUpImpOrForward

图片.png

  1. 在818.2中找到lookUpImpOrForward的源码实现。

图片.png

  1. lookUpImpOrForward这个函数的最后的返回值是imp,证明这是我们要找的。

图片.png

五、lookUpImpOrForward的源码解析

这里只挑源码复制在下面,官方的注释看情况,对探索有用的会放出来,解析都在下面的备注里 :

1. 源码及解析

IMP lookUpImpOrForward(id inst, SEL sel, Class cls, int behavior)
{
    
    //定义一个forward_imp,并给它赋值为(IMP)_objc_msgForward_impcache
    //这个不是没用的,这个后面会有用
    const IMP forward_imp = (IMP)_objc_msgForward_impcache;
    IMP imp = nil;
    Class curClass;
    
        runtimeLock.assertUnlocked();
    
    //判断类是否已经初始化过
    if (slowpath(!cls->isInitialized())) {
        behavior |= LOOKUP_NOCACHE;
    }
    
    //加锁
    runtimeLock.lock();
    
    //检查调用方法的这个类是否是已知或者说已经加载
    checkIsKnownClass(cls);
    
    //懒加载类、加载类、并且递归加载父类、元类,并保证它们是已经初始化的
    cls = realizeAndInitializeIfNeeded_locked(inst, cls, behavior & LOOKUP_INITIALIZE);
    
    runtimeLock.assertLocked();
    
    //将cls赋值给curClass,方便下面的操作,curClass会变成cls的父类,甚至父类的父类,直到NSObject的父类为nil为止
    curClass = cls;
    
        //死循环,只有goto和break才能跳出
    for (unsigned attempts = unreasonableClassCount();;) {
        
        //这里的if永远进不去,原因是isConstantOptimizedCache这个函数。
        //下面会有isConstantOptimizedCache的源码图
        if (curClass->cache.isConstantOptimizedCache(/* strict */true)) {
#if CONFIG_USE_PREOPT_CACHES //宏定义,arm64是1
            imp = cache_getImp(curClass, sel);
            if (imp) goto done_unlock;
            curClass = curClass->cache.preoptFallbackClass();
#endif
        } else {
     
            // curClass method list.
            // 获取cls的methods列表中的sel的imp
            Method meth = getMethodNoSuper_nolock(curClass, sel);
            // 如果能找到imp,直接去到done流程
            if (meth) {
                imp = meth->imp(false);
                goto done;
            }
            //如果curClass的methods列表中没有sel的imp,
            //则令curClass变成自己的父类,并且判断父类是否为nil。
            //如果curClass的父类为nil,则证明此时的curClass就是NSObject
            if (slowpath((curClass = curClass->getSuperclass()) == nil)) {
                // No implementation found, and method resolver didn't help.
                // Use forwarding.
                //curClass为NSObject的时候会进入if
                //cls的整条继承链都找不到sel的imp实现,则给imp赋值为forward_imp,并且跳出for循环
                imp = forward_imp;
                break;
            }
        }
        
        // Halt if there is a cycle in the superclass chain.
        //如果在父类继承链中存在循环,则停止for循环
        if (slowpath(--attempts == 0)) {
            _objc_fatal("Memory corruption in class list.");
        }

        // Superclass cache.
        // 在父类的缓存中找这个方法的imp是否缓存过
        imp = cache_getImp(curClass, sel);
        
        //如果imp已经变成了forward_imp,也就是当前这个循环中,curClass已经是NSObject了,则跳出for循环
        if (slowpath(imp == forward_imp)) {
            // Found a forward:: entry in a superclass.
            // Stop searching, but don't cache yet; call method
            // resolver for this class first.
            break;
        }
        
        //如果父类的缓存中有这个方法的imp,去到 done 流程
        if (fastpath(imp)) {
            // Found the method in a superclass. Cache it in this class.
            goto done;
        }
    }
    
    // No implementation found. Try method resolver once.
    // 找完NSObject的方法列表和缓存都找不到imp,则尝试一次动态决议
    // behavior的值会控制动态决议只进行一次
    if (slowpath(behavior & LOOKUP_RESOLVER)) {
        behavior ^= LOOKUP_RESOLVER;
        return resolveMethod_locked(inst, sel, cls, behavior);
    }
    
     done:
    
    //如果找到了imp,则将imp和sel存入到cls,也就是调用类的缓存中
    if (fastpath((behavior & LOOKUP_NOCACHE) == 0)) {
#if CONFIG_USE_PREOPT_CACHES
        while (cls->cache.isConstantOptimizedCache(/* strict */true)) {
            cls = cls->cache.preoptFallbackClass();
        }
#endif
        //将imp和sel存到类的缓存中
        log_and_fill_cache(cls, imp, sel, inst, curClass);
    }
    
    //解锁
 done_unlock:
    runtimeLock.unlock();
    
    // 找不到imp,直接返回nil
    if (slowpath((behavior & LOOKUP_NIL) && imp == forward_imp)) {
        return nil;
    }
    
    //返回imp。
    return imp;
}

2. isConstantOptimizedCache源码

图片.png

  1. 非真机环境的情况下,isConstantOptimizedCache的方法实现就在定义的后面,就是{ return false; },所以非真机的isConstantOptimizedCache是false。

  2. 如果是真机的环境下,需要全局搜索isConstantOptimizedCache的代码实现,找到如下 :

图片.png

  1. lookUpImpOrForward中,isConstantOptimizedCachestrict参数都是赋值为true的,所以isConstantOptimizedCache的返回值都是false。

六、lookUpImpOrForward流程梳理

(1). 对调用方法的类进行判断是否合规,不合规的将它变成合规的。然后将cls交给curClass进行操作。

(2). 定义一个循环。

(3). 查找类的方法列表中是否存有sel的imp。如果有imp,跳出循环,将sel和imp存到类的缓存中,并返回imp。

(4). 如果类的方法列表中没有imp,则查找父类的缓存中,是否有sel的imp存在。如果父类的缓存中有imp,跳出循环,将sel和imp存到类的缓存中,并返回imp。

(5). 如果父类的缓存中也没有sel的imp,则继续循环,查找父类的方法列表中是否有imp,查找父类的父类的缓存中是否有imp。循环到已经查找完NSObject的方法列表和缓存中是否有imp。

如果循环途中找到了imp,则会跳出循环,将sel和imp存储到类的缓存中,并返回imp。

如果沿着继承链,找到NSObject的方法列表和缓存中都没有imp,则给imp赋值为_objc_msgForward_impcache

(6). 如果找到NSObject都找不到sel的imp,那么执行一次动态决议。

(7). 如果动态决议找到了,则将imp和sel存到类的缓存中。

(8). 如果动态决议找不到,则直接返回nil。