阅读 206

OC 原理探索:方法的慢速查找流程

前言

OC 原理探索:objc_msgSend 流程 中我们对objc_msgSend流程进行了分析,objc_msgSend流程是方法的快速查找流程,我们今天来探索一下方法的慢速查找流程

准备工作

一、汇编缓存找不到

汇编源码回 C++ 源码

我们在 OC 原理探索:objc_msgSend 流程 中有说到如果没有缓存命中的话,会调用__objc_msgSend_uncached函数,我们去源码中查找这个函数。

image.png

  • __objc_msgSend_uncached函数中找到了MethodTableLookup,继续查找。

image.png

  • 在当前文件中找到了MethodTableLookup的定义,并在函数中找到了_lookUpImpOrForward,去查找_lookUpImpOrForward函数。

image.png

  • 我们全局查找,但并没有找到_lookUpImpOrForward相关的定义,怎么办呢,去掉_去查找lookUpImpOrForward

image.png

  • 成功找到了lookUpImpOrForward函数,也宣布从汇编源码正式回到了C++ 源码

为什么缓存用汇编

1. 速度快

缓存的目的就是为了提高效率,汇编更快,让当前对象快速的找到缓存,优化方法查找的时间。

2. 动态化

汇编更加动态化,我们调用方法时,参数是未知的,C++/C参数要非常的明确

二、慢速查找流程的准备

先看一下lookUpImpOrForward函数的整体结构:

image.png

  • 下面对lookUpImpOrForward中的一些函数调用做解析。

checkIsKnownClass

checkIsKnownClass的关键路径:

image.png

  • 检查当前class是否已经注册到缓存表中。

realizeAndInitializeIfNeeded_locked

先看下函数的实现:

image.png

  • 这个函数中有两条实现路径,针对路径中的关键函数做一些解析。

1. realizeClassWithoutSwift

查找路径:realizeClassMaybeSwiftAndLeaveLocked -> realizeClassMaybeSwiftMaybeRelock -> realizeClassWithoutSwift,对函数中的关键代码做一些说明。

image.png

  • 一些rwro的操作。

image.png

  • 递归查找元类,如果没有初始化,进行初始化操作。
  • 做这个的意义是什么呢,因为方法在当前类找不到的话,会去父类查找直至根类。

2. initialize

查找路径:initializeAndLeaveLocked -> initializeAndMaybeRelock -> initializeNonMetaClass -> callInitialize

void callInitialize(Class cls)
{
    ((void(*)(Class, SEL))objc_msgSend)(cls, @selector(initialize));
    asm("");
}
复制代码
  • 我们平时也会用的initialize函数,这里找到了调用它的地方。

三、二分查找流程

lookUpImpOrForward中的查找流程,核心就是下面这个for 循环

image.png

  • 下面对函数进行解析。

共享缓存

image.png

  • 共享缓存中查找,防止临时有插入进来的方法。

查找函数 getMethodNoSuper_nolock

点击进入getMethodNoSuper_nolock

image.png

  • 方法列表是个二维的数组,循环遍历数组,在循环中通过search_method_list_inline函数去通过sel查找imp
  • 查找二分查找方法:search_method_list_inline -> findMethodInSortedMethodList -> findMethodInSortedMethodList,下面进行解析。

二分查找 findMethodInSortedMethodList

image.png

  • 我们假设count = 16,开始findMethodInSortedMethodList的源码分析。
  • probe = base + (count >> 1);
    • probe = base + (16 >> 1) -> probe = base + (10000 >> 1) -> probe = base + 8,获取列表中间的位置,因为是平移8各单位,实际是列表的第9个位置。
  • uintptr_t probeValue = (uintptr_t)getName(probe);
    • probeValue = 中间位置的值。
  • if (keyValue == probeValue),中间位置的值和我们寻找的SEL值是否相等。
  • 如果相等:
    • image.png 向前遍历找到第一个category的方法。
    • return &*probe;,返回找到的方法。
  • 如果不等:
    • if (keyValue > probeValue),判断SEL值是否大于中间值
    • 如果SEL大:
      • base = probe + 1;base = 8 + 1 = 9,平移9个单位,实际是列表的第10个位置。
      • count--;count = 15 = 1111
      • count >>= 1count = 7
    • 如果SEL小:
      • count >>= 1count = count >> 1 = 8

二分查找算法就解析到这里,接下来就是继续遍历,原理是一样的。如果有同学还是不理解可以用下面的代码进行调试,代码是我仿照上面的源码写的。

可调试 二分查找算法


int search(int sel,int *a,int count);

int main(int argc, const char * argv[]) {
    @autoreleasepool {

        int a[16] = {1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16};
        int sel = 12;
        
        int index = search(sel, a, 16);
        NSLog(@"index:%d",index);
    }
    return 0;
}
        
int search(int sel,int *a,int count)
{
    int *base = a;
    int *probe;
    
    for (; count != 0; count >>= 1) {
    
        probe = base + (count >> 1);
        int probeValue = *probe;
        
        if (sel == probeValue) {
            return probeValue;
        }
        
        if (sel > probeValue) {
            base = probe +1;
            count--;   
        }
    }
    return -1;
}
复制代码

四、慢速查找递归流程

上面已经探索了通过getMethodNoSuper_nolock函数获取MethodMethod可能找到也可能找不到,接下来针对两种情况继续探索。

成功找到 Method

如果成功找到Method,接下来会通过goto语句跳到下面的done

image.png

  • done中的关键方法是log_and_fill_cache,点击进入查看实现。

image.png

没有找到 Method

如果没有找到Method,接下来会去父类中查找,我们继续下面流程的探索。

image.png

  • 通过curClass = curClass->getSuperclass()这句代码,curClass已经成为了父类
  • 如果父类nilimp = forward_imp;,结束for 循环
  • 如果父类不为nil,调用cache_getImp(curClass, sel),对父类进行缓存的快速查找,接下来对cache_getImp进行解析。

1. cache_getImp

全局搜索cache_getImp

image.png

  • 我们发现并没有找到cache_getImp的实现,猜测应该是去到了汇编,我们去汇编中查找。

image.png

  • 在汇编中成功找到_cache_getImp,在 OC 原理探索:objc_msgSend 流程 中我们已经对CacheLookup进行过详细的分析,是进行的快速查找流程,但是在这里有所不同。
  • 缓存没找到的情况下,这里的LGetImpMissDynamic是直接返回了nil,不像上一篇中是调用了__objc_msgSend_uncached函数。

2. 递归调用

因为cache_getImp返回nil,所以继续执行for 循环,来到下面的代码:

image.png

  • 继续getMethodNoSuper_nolock函数查找方法,不过这个时候curClass已经变成了superClass
  • 如果还是没有找到,将会继续查找superClass直到NSObject的父类nil,这时imp = forward_impimp返回。

imp = forward_imp,将imp返回以后又会发生什么呢,下一篇将会继续探索。

点个赞支持一下吧!!😄😄😄

文章分类
iOS
文章标签