十二、objc_msgSend(方法在缓存中的查找流程)

1,652 阅读3分钟

本文由快学吧个人写作,以任何形式转载请表明原文出处

一、资料准备

objc4-818.2

对应mac的版本是11.1。可根据自己的系统版本挑选可以进行调试的源码。

二、思路

  1. 为什么要看objc_msgSend?

调用方法的本质就是objc_msgSend

  1. objc_msgSend在哪里?

在runtime中

  1. objc_msgSend是什么写的?

汇编

  1. objc_msgSend为什么用汇编?
  1. C语言是静态语言。不可能写一个函数用来保留未知的参数,还要做到跳转任一函数的指针。

  2. 汇编是更接近机器指令的语言,objc_msgSend的功能就决定了它需要更快。

  1. 为什么要用objc4的源码?

因为objc_msgSend的源码是在libobjc.A.dyld库中,objc4里有这个库。

三、创建代码

  1. 在818.2中创建一个类(我创建的是JDPerson),继承于NSObject。并且定义一个实例方法。然后在main.m中创建实例,并调用这个实例方法。并在调用实例方法这一行打断点。

图片.png

图片.png

  1. 打开汇编

图片.png

  1. 运行项目

四、有关汇编的简单基础

  1. 汇编中有个东西叫寄存器,arm64架构下有31个通用寄存器,每个寄存器都是64位。访问它们用:x0x30

  2. 访问寄存器的低32位用 :w0w30

  3. 寄存器的x0到x7存储的是函数的前8个参数。例如:objc_msgSend的参数是id selfSEL sel,分别对应着x0和x1。另外x0还是返回值的存储位置。

五、objc_msgSend的源码

1. 如何找到objc_msgSend的源码

在818.2的源码中搜索objc_msgSend,找到arm64环境下的objc_msgSend,并在其中找到带ENTRYobjc_msgSend,因为ENTRY是进入的意思。

图片.png

2. objc_msgSend汇编解析

1. ENTRY _objc_msgSend,对消息接收者(id self,sel _cmd)判断处理,tagged pointer判断处理。

图片.png

2. GetClassFromIsa_p16关于isa指针的处理,获得class(类)。

文件内搜索GetClassFromIsa_p16。找到源码后,对其中的#if SUPPORT_INDEXED_ISA不需要看它里面的内容,找__LP64__

图片.png

这里的ExtractISA还不知道是什么,全局搜索ExtractISA :

图片.png

这里的$1就是isa$0= $1 & #ISA_MASK。也就是类信息。

3. 回到主线,CacheLookup查找缓存

图片.png

(1). CacheLookup的注释 :

图片.png

(2). 一些宏定义,CacheLookup源码中会用到的 :

图片.png

图片.png

图片.png

图片.png

(3). CacheLookup源码解析 :

图片.png

图片.png

图片.png

图片.png

图片.png

(4). CacheHit命中缓存的源码 :

首先明确一点,CacheLookupNORMAL

图片.png

直接调用了一个TailCallCachedImp

(5).

图片.png

六、总结

  1. objc_msgSend会对消息的接收者进行判断处理。
  2. objc_msgSend中的GetClassFromIsa_p16会对isa进行处理,获取其中的类信息。
  3. objc_msgSend会在CacheLookup中通过类的内存平移获取到缓存,在缓存中拿到mask和buckets,通过掩码获取到单独的buckets。通过cache_hash获取sel的索引,通过遍历buckets中的bucket_t,查找缓存中是否有要调用的方法的实现。