iOS 底层探索09——慢速方法查找

这是我参与更文挑战的第11天,活动详情查看: 更文挑战

前言

为了更加深入的理解方法查找中的快速查找,我们从汇编代码里面摸索了很久过程很枯燥,但是收获也挺多,汇编分析暂时告一段落,今天我们着重看下方法查找过程中的慢速查找;

快速查找汇编回顾

快速查找缓存的汇编代码已经在之前文章说过了,这里不再赘述,只对汇编代码过程中容易出错的点进行说明;

_bucketsAndMaybeMask重点

  1. _bucketsAndMaybeMask 里面存的数据包含低16位的_maybeMask和高48位的buckets_t pointer两部分;
  2. 其中高48位是buckets_t pointer;低16位是_maybeMask 是capacity – 1;
  3. 取buckets_t的时候需要将bucketsMask & _bucketsAndMaybeMask才能得到;注意这里用的是bucketsMask不是_maybeMask!;
  4. bucketsMask是一个固定常数,而_maybeMask则是根据capacity – 1计算得出;

cache_hash 和 cache_next

  1. cache_hash方法是首次通过sel计算下标时候用到的方法;cache_next是二次哈希通过sel计算下标时候用到的;
  2. cache_hash内部的在计算前sel会先将sel转换成数值,然后>>7位,最后用处理后的sel & mask;这里用到的mask就是_maybeMask=capacity – 1;

循环流程

  1. 循环时,如果发现当前index不满足,则需要进行nidex–得到前面一个位置,在汇编代码中这里处理成了左移4个单位,即左移16个字节;
  2. 为什么左移4个byte位(左移16字节)呢?每个bucket都是IMP和SEL组成,共16个字节,前一个bucket的起始地址和当前bucket的地址地址中间隔了这16个字节,所以左移16相当于C++代码中的index–;

真机查看汇编

  1. 真机查看汇编和通过源码查看汇编基本一致,唯一区别的是要根据寄存器当前的数据,来确定是否位是当前调用的信息;
  2. 查看当前寄存器信息的LLDB指令register read x0,register read x1,分别获取当前的x0和x1寄存器,一般情况下,参数都是存放在x0-x7这8个寄存器中;
  3. 具体流程不再分析了;

类方法慢速查找

从快速查找到慢速查找

  1. 通过分析我们知道objc_msgSend的汇编代码在查找cache未命中的情况下会执行MissLabelDynamic,而MissLabelDynamic对应的参数则是 __objc_msgSend_uncached这个汇编方法;
  2. 在__objc_msgSend_uncached 这个汇编方法的内部我们找到了MethodTableLookup;
.macro MethodTableLookup
	SAVE_REGS MSGSEND
	// lookUpImpOrForward(obj, sel, cls, LOOKUP_INITIALIZE | LOOKUP_RESOLVER)
	// receiver and selector already in x0 and x1
	mov	x2, x16
	mov	x3, #3
	bl	_lookUpImpOrForward
	// IMP in x0 
	mov	x17, x0//x0寄存器作为返回体,说明方法查找是从上一步_lookUpImpOrForward方法里获取的结果
	RESTORE_REGS MSGSEND
.endmacro
复制代码
  1. 由于x0寄存器存放的是返回值信息,所以在MethodTableLookup方法里,x0寄存器出现的那行代码的上一行_lookUpImpOrForward极有可能就是查找imp的方法,是不是这样呢?
  2. 全局搜索lookUpImpOrForward 发现是由c++方法实现的,查看lookUpImpOrForward源码,最终证明了我们的猜想;
  3. 汇编方法调用C++方法时会在方法名前面添加_(下划线);

objc_msgSend使用汇编语言的优势

  1. 由于更加接近及其语言,汇编方法执行效率更高;
  2. 由于汇编语言是底层语言,不容易像上层语言那样被hook,安全性更高;
  3. 汇编语言是基于地址来执行的,在面对不同的参数类型时具有更高的灵活性,也更加的动态化;

慢速查找lookUpImpOrForward

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