objc_msgSend探索

256 阅读3分钟

一、概述

objc_msgSend这个方法对于iOS开发者来说是再熟悉不过了,无论是初学还是中级最后进阶资深都离不开这个方法。在iOS中调用方法实质上就是给某个对象发送消息。消息的发送在编译的时候编译器就会把方法转化为objc_msgSend这个函数。objc_msgSend有两个隐式的参数,消息的接收者和消息的方法名。objc_msgSend这个函数就能通过这两个隐形的参数找到对应的方法具体实现。

  1. 如果消息的接收者是实例对象,isa就指向该类对象,后再通过第二个参数方法名,去寻找对应的实现。
  2. 如果消息的接收者是类对象,isa就指向他的元类,就会去元类里面寻找对应的方法实现。

image.png

image.png

二、objc_msgSend方法的实现

我们从源码进行探索,源码版本号是objc4-838.1,全局搜索objc_msgSend,可以看到有很多,在arm64架构下的,有i386下的,有模拟器x86下面的,我们现在看一下在arm64架构下面的。

image.png

image.png

我们可以看到在真机arm64构架下,方法是由汇编写的,至于为什么会用汇编,那肯定是为了提高效率嘛。

三、对应的真机调试

image.png 1.首先进入objc_msgSend流程,cmp p0判断p0是否存在,我们从读取寄存器里面可以看到,X0寄存器是存了一个LGPerson的实例对象,X1是study方法。 image.png 2.通过x0 x/4gx打印出isa指针,isa指针地址是0x000021a104045789,恰好x13寄存器的地址就是0x000021a104045789,说明x13寄的是isa地址。通过isa指针找到对应的类,打印x16的地址,x16 = 0x0000000104045788  (void *)0x0000000104045760: LGPerson,存的就是类对象地址。 image.png 3.找到类对象了,下一步该通过类对象找方法的实现。

image.png 4.开始找方法,取出x16的class移到x15,通过x16找cache

image.png 5.ldr    x11, [x16, #0x10]通过内存平移可以得到x11,x11就是之前cache内的第一个_backetsAndMaybeMask 6.and    x10, x11, #0xfffffffffffe得到buckets的首地址x10。然后在cache进行方法查找。如果找到会CacheHit \Mode,找不到的话会调用_objc_msgSend_uncached,一直循环直到找到对应的方法。

image.png

image.png

消息的快速查找流程:

  1. 判断receiver(消息的接收者)是否存在。
  2. receiver->isa->class
  3. class内存平移->cache
  4. cache->buckets
  5. 遍历buckets -> bucket(sel,imp)对⽐sel
  6. 如果bucket(sel,imp)对⽐sel 相等 --> cacheHit --> 调⽤imp
  7. 如果cache⾥⾯没有找到对应的sel --> _objc_msgSend_uncached

四、方法的快速查找

1.方法在Cache内找不到,那就会去调用_objc_msgSend_uncached函数

image.png 2.在_objc_msgSend_uncached函数内部可以看到调用MethodTableLookup,内部又会跳转_lookUpImpOrForward

image.png

image.png

image.png

image.png

消息的慢速查找

lookUpImpOrForward -> 先找当前类的methodList -> superClass的cache -> superClass的 methodList -> 直到superClass为nil。 若父类为空的时候,还未找到,则会进入消息转发。