关于方法查找的流程(快速+慢速)

回顾

为什么要做一个回顾?

原因:

由于之前的那篇博客个人觉得太长了,且关于方法查找这一块,很多的点集中在一起,不好梳理,现在跳出具体实现的每一步的步骤,去总览整个方法查找的流程步骤,另外也是温故而知新。

1.首先,对象进来会判断他是不是存在,不存在就直接返回zero,说明他是支持给nil对象发送消息的,只不过不起效果罢了,最典型的就是你声明一个button或者label,不用创建,在编译时给他赋值也不会报错。

2.其次拿到对象的isa指针,通过&mask 获取到类的消息。

3.进入cacheLookup->再到LLookupStart,根据不同的架构,走不同的流程,目前就真机而言,会根据Config_USER_PREOPT_CACHES,这个值是1,然后根据是否是A12架构判断走不同的流程,但是两种流程只是顺序不同,所以从普通的非A12来看,第一步,通过平移获取到类的cache。第二步,tbnz p11, #0, LLookupPreopt\Function,tbnz是判断后面的这个p11的0号位置是不是0,如果不是0,那么就走LLookupPreopt(这里有通过共享缓存加载,暂时不扩展。),如果是往下走。(【补充】那么这里为什么要判断呢?因为在整个64位里面,真机环境下,bucket在低48位,mask在高16位,而在mask里面,还留了四个位置叫maskZeroBits,给objc_msgSend使用,可以通过他判断是否有preopt_cache_t)。接着(and p10, p11, #0x0000fffffffffffe),0x0000fffffffffffe这是后48为1,预示着取bucket里的信息。即p10jiushi bucket。**总结:**整个第三步就是通过类获取cache然后获取bucket。

1624957245398.jpg

4.接下来要获取对应的index,找到这个bucket,个人觉得这里很多地方都没有讲清,但是作为流程的话,我觉得理解是第一位。整个index的获取是:先把sel右移7位(因为在哈希插入的时候就是右移7位)再&mask就得到了index,这个mask把bucket移除,也就是右移48位获取。

截屏2021-06-29 下午6.18.51.png
5.把获取到index左移4位,这里的ptrshift是3,左移4位相当于16,index16就是位移的个数,这也是当前首次查找的bucket,通过获取bucket然后获取bucket里的sel和imp,与我们当前的sel对比是否是同一个。如果是就缓存命中,如果不是继续循环。

截屏2021-06-29 下午6.23.15.png
6.接下来就是循环,把【X13】寄存器里的数据imp给了p17,sel给了p9,然后比较p9和p1,相同的话就走cachehit缓存命中,不同走第三步,判断sel是不是0,有没有取到值,可以看到最终第三步又跳到了第一步,直到要么第二步找到了,要么找不到MissLabelDynamic。

7.循环遍历

截屏2021-06-29 下午6.32.24.png

截屏2021-06-29 下午6.32.55.png

当前循环会走bucket–,直到等于0到头了,这时候会直接左移到头重头开始–,确保全部遍历结束,首先比较p9和p1,如果想同就缓存命中,如果不同继续走,首先比较p9是不是0,有没有,另外也要比较sel是否大于bucket的frist_probed,防止循环过来以后重复比较。

8.如果没有命中,就会走MissLabelDynamic,调用__objc_msgSend_uncached.

探索__objc_msgSend_uncached

接下来就要看,如果没有在缓存中找到对应的方法,系统将如何处理?

第一步:

840BC475-7260-48A6-8855-AFF8091480FD.png

第一种方式:通过全局搜索__objc_msgSend_uncached,找到了MethodTableLookup方法,在这个方法的定义里找到了_lookUpImpOrForward方法,全局搜索再也搜索不到了,去掉下划线,lookUpImpOrForward回到了c。。
第二种:直接在代码里打断点,走断点最后也会走到这个方法。。。

第二步:

截屏2021-06-29 下午9.21.36.png

汇编方式查找快速,可以方便用户快速找到对应的方法,然而有些参数未知,c语言可以对未知的参数定义

lookUpImpOrForward–遍历–慢速查找。

初始化

首先进来会判断类是否初始化(isInitialized),然后再判断父类以及元类是否初始化,只要有一个初始化,其他相关的父类、元类都会被初始化,从中找方法(方法都在ro、rw中)。

递归

首先还是会到共享缓存中查找一遍,也就是Config_USER_PREOPT_CACHES。
接着进入到方法查找,首先调用的是getMethodNoSuper_nolock。cls->data()->methods(),获取类的方法列表。遵循方法查询分别会走,search_method_list_inline->findMethodInSortedMethodList->M1走small,常规走big,以常规为例->findMethodInSortedMethodList

截屏2021-06-29 下午9.57.05.png

二分查找之苹果的高明之处之co某人的高明(讲解)之处!!~

举个栗子如果数量是8(1000),此时probe = 0 + 4 = 4,(1000>>1 == 4),如果比较出方法相同,此时还要再比较,这个时候如果说分类的方法和本类一致,那么返回的是当前probe–;如果要查到的位置还要大于,再查找,此时count = 7,base = 5,count又会>>1,count = 3,此时probe = 5+(0010>>1=1)= 6;

截屏2021-06-29 下午10.09.23.png

如果没有从本类中找到,会到父类中找到,调用cache_getImp,父类的快速查找,调objc_msgSend,快速如果没有找到,再去慢速,没找到再去父类的父类。。不停的递归。。。

慢速查找以后的处理

如果找到了,系统会调用缓存inert方法,插入到缓存中,下次方便从缓存中读取,具体是先调用了fill_log方法,然后调用insert方法。

如果还是没有找到,imp = forward_imp.

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享