回顾
为什么要做一个回顾?
原因:
由于之前的那篇博客个人觉得太长了,且关于方法查找这一块,很多的点集中在一起,不好梳理,现在跳出具体实现的每一步的步骤,去总览整个方法查找的流程步骤,另外也是温故而知新。
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。
4.接下来要获取对应的index,找到这个bucket,个人觉得这里很多地方都没有讲清,但是作为流程的话,我觉得理解是第一位。整个index的获取是:先把sel右移7位(因为在哈希插入的时候就是右移7位)再&mask就得到了index,这个mask把bucket移除,也就是右移48位获取。
5.把获取到index左移4位,这里的ptrshift是3,左移4位相当于16,index16就是位移的个数,这也是当前首次查找的bucket,通过获取bucket然后获取bucket里的sel和imp,与我们当前的sel对比是否是同一个。如果是就缓存命中,如果不是继续循环。
6.接下来就是循环,把【X13】寄存器里的数据imp给了p17,sel给了p9,然后比较p9和p1,相同的话就走cachehit缓存命中,不同走第三步,判断sel是不是0,有没有取到值,可以看到最终第三步又跳到了第一步,直到要么第二步找到了,要么找不到MissLabelDynamic。
7.循环遍历
当前循环会走bucket–,直到等于0到头了,这时候会直接左移到头重头开始–,确保全部遍历结束,首先比较p9和p1,如果想同就缓存命中,如果不同继续走,首先比较p9是不是0,有没有,另外也要比较sel是否大于bucket的frist_probed,防止循环过来以后重复比较。
8.如果没有命中,就会走MissLabelDynamic,调用__objc_msgSend_uncached.
探索__objc_msgSend_uncached
接下来就要看,如果没有在缓存中找到对应的方法,系统将如何处理?
第一步:
第一种方式:通过全局搜索__objc_msgSend_uncached,找到了MethodTableLookup方法,在这个方法的定义里找到了_lookUpImpOrForward方法,全局搜索再也搜索不到了,去掉下划线,lookUpImpOrForward回到了c。。
第二种:直接在代码里打断点,走断点最后也会走到这个方法。。。
第二步:
汇编方式查找快速,可以方便用户快速找到对应的方法,然而有些参数未知,c语言可以对未知的参数定义
lookUpImpOrForward–遍历–慢速查找。
初始化
首先进来会判断类是否初始化(isInitialized),然后再判断父类以及元类是否初始化,只要有一个初始化,其他相关的父类、元类都会被初始化,从中找方法(方法都在ro、rw中)。
递归
首先还是会到共享缓存中查找一遍,也就是Config_USER_PREOPT_CACHES。
接着进入到方法查找,首先调用的是getMethodNoSuper_nolock。cls->data()->methods(),获取类的方法列表。遵循方法查询分别会走,search_method_list_inline->findMethodInSortedMethodList->M1走small,常规走big,以常规为例->findMethodInSortedMethodList
二分查找之苹果的高明之处之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;
如果没有从本类中找到,会到父类中找到,调用cache_getImp,父类的快速查找,调objc_msgSend,快速如果没有找到,再去慢速,没找到再去父类的父类。。不停的递归。。。
慢速查找以后的处理
如果找到了,系统会调用缓存inert方法,插入到缓存中,下次方便从缓存中读取,具体是先调用了fill_log方法,然后调用insert方法。
如果还是没有找到,imp = forward_imp.