本文由快学吧个人写作,以任何形式转载请表明原文出处
一、资料准备
对应mac的版本是11.1。可根据自己的系统版本挑选可以进行调试的源码。
二、思路
- 为什么要看objc_msgSend?
调用方法的本质就是objc_msgSend
- objc_msgSend在哪里?
在runtime中
- objc_msgSend是什么写的?
汇编
- objc_msgSend为什么用汇编?
C语言是静态语言。不可能写一个函数用来保留未知的参数,还要做到跳转任一函数的指针。
汇编是更接近机器指令的语言,
objc_msgSend的功能就决定了它需要更快。
- 为什么要用objc4的源码?
因为objc_msgSend的源码是在
libobjc.A.dyld库中,objc4里有这个库。
三、创建代码
- 在818.2中创建一个类(我创建的是JDPerson),继承于NSObject。并且定义一个实例方法。然后在main.m中创建实例,并调用这个实例方法。并在调用实例方法这一行打断点。
- 打开汇编
- 运行项目
四、有关汇编的简单基础
汇编中有个东西叫寄存器,arm64架构下有31个通用寄存器,每个寄存器都是64位。访问它们用:
x0到x30。访问寄存器的低32位用 :
w0到w30。寄存器的x0到x7存储的是函数的前8个参数。例如:objc_msgSend的参数是
id self和SEL sel,分别对应着x0和x1。另外x0还是返回值的存储位置。
五、objc_msgSend的源码
1. 如何找到objc_msgSend的源码
在818.2的源码中搜索objc_msgSend,找到arm64环境下的objc_msgSend,并在其中找到带ENTRY的objc_msgSend,因为ENTRY是进入的意思。
2. objc_msgSend汇编解析
1. ENTRY _objc_msgSend,对消息接收者(id self,sel _cmd)判断处理,tagged pointer判断处理。
2. GetClassFromIsa_p16关于isa指针的处理,获得class(类)。
文件内搜索GetClassFromIsa_p16。找到源码后,对其中的#if SUPPORT_INDEXED_ISA不需要看它里面的内容,找__LP64__。
这里的ExtractISA还不知道是什么,全局搜索ExtractISA :
这里的$1就是isa,$0= $1 & #ISA_MASK。也就是类信息。
3. 回到主线,CacheLookup查找缓存
(1). CacheLookup的注释 :
(2). 一些宏定义,CacheLookup源码中会用到的 :
(3). CacheLookup源码解析 :
(4). CacheHit命中缓存的源码 :
首先明确一点,CacheLookup是NORMAL。
直接调用了一个TailCallCachedImp。
(5).
六、总结
- objc_msgSend会对消息的接收者进行判断处理。
- objc_msgSend中的
GetClassFromIsa_p16会对isa进行处理,获取其中的类信息。 - objc_msgSend会在
CacheLookup中通过类的内存平移获取到缓存,在缓存中拿到mask和buckets,通过掩码获取到单独的buckets。通过cache_hash获取sel的索引,通过遍历buckets中的bucket_t,查找缓存中是否有要调用的方法的实现。