阅读 132

底层探索 -- OC消息机制(二)lookUpImp慢速查找

本篇重点

  1. lookUpImpOrForward慢速查找流程及其内部细节分析。

一、objc_msgSend_uncached分析

快速查找中CacheHit的逻辑已经分析过了,接下来分析没找到时会进入的__objc_msgSend_uncached 方法,看看其内部的对于查找是如何处理的:

V3z4XeEuLIk_j4z0jDrL6hKRoqMZliBNSXt1pOLkzsQ.png

  1. objc_msgSend_uncached 主要的逻辑内容在 MethodTableLooup中,看其名称也能猜到:准备去找Cls->method_list了.

  2. 注意注释中的的behavior的参数的值 ==3

  3. 将信息存入了寄存器后,就BL进入_lookUpImpOrForward源码,开始查找。

二、lookUpImpOrForward分析

进入C++源码定位到方法后,来分析一下_lookUpImpOrForward中的实现逻辑:

-nu8__CDwb0pGXFphMQ-OZ1o7XoyIOqSoURzN3ZQxYk.png

如图所示,整个_lookUpImpOrForward主要代码的含义已经标注完成了,现在先对其整体逻辑进行一下梳理,然后再对某些局部的细节展开来分析一下:

  • 上半部分为准备工作:创建默认IMP、检查Cls的合法、递归实现Cls继承链和初始化MetaCls,准备好相关类的rw为查找method_list作准备。

  • 下半部分为判断查找

    1. 命中则 go done

    2. 未命中则 继续循环

    1. iOS会再次进入汇编cache_getImp查找一次,内部逻辑依然是快速查找的CacheLookup

    2. 其他则进入getMethodNoSuper二分遍历Cls的method_list

    1. realizeAndInitialize后与当前Cls相关的一系列Cls都已经完成初始化,进for先是if...else判断。

    2. 完成if...else如果依然未命中,此时CurCls==Cls.SuperCls,父类再次进入cache_getImp 查找。

    3. for循环外的if逻辑,如图标注。

整体逻辑梳理如上,现在开始对其中的局部细节展开来分析。

2.1 forward_imp

由刚才对整体流程的分析可知,这个IMP是最后找完了整个继承链-直到NSObject还没有找到的时候返回的IMP,日常开发中在控制台见到它的时候是这个样子:

BLMs0ENFb3TjLfwzd_5bYerOHdOwvAd0V4pwvZqEvro.png

那么其中的 unrecognized... 的是怎么返回来的呢,现在就进入源码看看:

v-MtwgtHghzvOH6f6YTv3Vf0q7AQKH1pODMII_pMHpE.png

搜索后发现forward_imp 的真实实现也是汇编,进入可以看到逻辑并不复杂,使用adrp 读取了__objc_forward_handler 的地址并且存入了存放IMP的x17中,那么再搜索一下_objc_forward_handler,看看这个被加载的句柄具体是什么内容:

op6rrrGXNuBV49VuFSxGu24mrSChIG0xfWYyx4MVysc.png

如图中标注,根据__OBJC2__ 做了方法区分,返回包括类、元类的标识符“+”、“-”,类名等,统统拼接好了,这就是日常开发中见到的那个unrecognized - IMP

2.2 checkIsKnownClass

如官方是注释所说,此方法为了防止CFI攻击(暂时我也不知道具体是什么)、确保Cls的合法性和安全,所以方法的内部主要是去查当前运行的Cls是否在共享缓存、已加载Image的数据段、或者是否被obj_allocateClassPair创建的,且在Cls的Cache中存有 uint16_t witness标识用于验证。

NQQwCxsujVy2SyUGM1iZhzxhGwV_av71FpanPqiO7UQ.png

  • 使用witness去数据查询,大概率会返回Ture。

  • 如果if判断失败的话,则去查存allocatedClasses表。

2.3 realizeAndInitializeIfNeeded_locked

这个方法主要是为了接下来的for循环而处理cls及Cls.Superclss等一系列相关的。现在来看看其内部实现:

Irri75DwQ6z1Y0NTuGBdWz7LNLPWaZL1gkWtcNviavw.png

其内部主要就是2个if判断,同时也是涉及着isa关系图 中两个链条的实现及初始化:

  1. 首先是判断cls->isRealized ,判断的依据是data()->flag31位,if内方法的目的就是实现当前Cls及SuperCls这个链条上的Cls.rw

    • 对于OC来说,if内的方法会具体实现到realizeClassWithoutSwift 随后展开分析。

    • 另一个分支,则是Swift。

  2. 其次是判断cls->isInitialized ,判断的依据是data()->flags29位,if内方法的目的就是初始化当前Cls及Metal这个链条上的Cls

    • if内的方法会具体实现到initializeNonMetaClass 随后展开分析。

1. realizeClassWithoutSwift

gCycJ06ZgBtrQPFNlzGQaRlXDqTqU3pPhzsh46skNRA.png

  • 截取了realizeClassWithoutSwift中重要的、能够体现其主要逻辑的部分,通过之前对于类de数据结构分析(总) ,图中代码的意思应该是见文知意的。

2. initializeNonMetaClass

dufBp6WWzBCcV05FuETPtObLIPp-44axS2GtQmYSClM.png

  • 依旧截取了initializeNonMetaClass 中能够体现其主要逻辑的代码,从入口开始现在当前Cls的SuperCls开始初始化,一级一级往下初始化,初始化完成后没有什么问题则直接return

2.4 getMethodNoSuper_nolock

接下来,看看getMethodNoSuper_nolock中是如何对method_list查找的:

KTuk46e_Yy7GmhbpbpduGdFvY7ZttjXYXHr7WLbIxQk.png

因为method_array_t 是二维结构,所以在这里先有一层for循环,继续深入就是findMethodInSortedMethodList 了:

zcGVETcxol4nZnRQkiDNUAJiJ7pq4bPzhuuGMFt4h4k.png

如图,使用二分查找对于method_list_t进行检索,其中的>>1就是二分的关键,在图中也已标注。其他逻辑相对也不难,只有2个点容易让人产生疑惑,如图中所标注,现在来详细解释一下:

  1. 命中目标SEL后,为什么还要Probe--向前查找同名SEL,最后返回最靠前的?

    多数一句:
    探索分析到这里时,起初不解为什么Probe-- ,只是通过注释知道和Category有关。在网上查找相关问题时,有的博客里写:
    这里Probe--是因为要按照栈(Stack)的FIFO原则取值。类别是后覆盖了主类的的方法,类别的方法后入栈,所以要向前去取。
    离大谱了!method_list_t继承自entsize_list_tt ,其子类还有property_list_tivar_list_t。这些家伙的父类entsize_list_tt是拥有iterator 的结构体啊,官方注释也有标注:Generic implementation of **an array **of non-fragile structs. 怎么会和Stack有关系呢???

    • 首先,注释中提到了,这个Probe--和类别(Category) 有关,具体说就是类别中的同名方法会覆盖主类中的方法。 (同时当前问题或者图中这个查找算法也可以反过来解释:为什么最后编译的类别中的同名方法会覆盖主类中方法

    • 其次,主类方法不是被覆盖了吗?类别的方法不是追加的吗?怎么还要Probe--向前查找?这些问题关键在于:类别的method_list加入主类method_list时的方法attachLists()中的插入算法:开辟新的数组后,method_list的插入逻辑是倒叙的,也就是新的list在前,旧的在后。 (关于attachLists的分析会在之后分析到Category时做展开)

  2. 未命中SEL的时候,为什么要做Count--

    • --之后,可以将base当前位置的占用减去,在下一次循环中将probe 的位置移动至剩余查找量的2/1处。

    • 画了个简单的草图,来理解一下:

      J-gIPbUuu0q2M_sCfxDBkCqIld4-r6mz7cIHfJ_R9Os.png

三、总结


以上就是本篇对于objc_msgSend_uncached慢速查找流程的全面分析,除了对于源码逻辑的文字分析外,大部分源码的逻辑解释分析都附带到了截图中,同样需要认真看,同样需要认真看。

至此,OC消息机制中-关于IMP查找的所有源码就已经全部分析完了,接下来则进入关于方法的动态决议部分的分析。

篇中分析、记录的内容如有帮助,欢迎点赞、收藏、评论。如果错误,欢迎指出????????‍♂️。


作者:红扑扑的小脸儿
链接:https://juejin.cn/post/7066360938613440543


文章分类
代码人生
版权声明:本站是系统测试站点,无实际运营。本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 XXXXXXo@163.com 举报,一经查实,本站将立刻删除。
相关推荐