11.源码解读objc_msgSend
前言
本文基于
objc4-818.2源码iOS14.6 iPhoneX
源码版本、系统版本、设备不同的话,真机调试的汇编会和源码有不同。自行调试的时候需要注意
源码中对于重要部分做了注释,
...表示省略了一些其他架构或者无关紧要的代码为了方便阅读,汇编代码中的做了
缩进调整和分块注释,阅读起来应该会轻松不少电脑端阅读体验更佳哦本文看着会比较累,用了个看着比较轻松愉悦的主题。
配上一杯 ☕️ 体验更佳
objc_msgSend相关的内容很多,此文会持续更新、修正具体更新内容可以查看
更新记录
欢迎收藏、交流、抓虫
更新记录
20210701
首次发布
一、平时我们是如何调用方法的?
有三种方式进行调用: OC Method
NSObject API
objc API。
API的层级结构如下:

1.1 实验三种方式
需要注意的点:
#import <objc/message.h>Xcode 12以下Build setting->objc_msgSend->Enable Strict Checking of objc_msgSend Calls->NO
Xcode 12以上需要对 objc_msgSend 转类型,才能正常调用
输出:都能够正常调用
1.2 调用父类方法
RYSubModel 继承 RYModel ,我们看看C++文件中 isSub 的情况。
这里 [super dosomethingA] 转成C++后,发现一个 objc_msgSendSuper 方法,通过它来调用父类的方法。
1.3 objc_msgSendSuper
我们试试能不能直接调用 objc_msgSendSuper。
objc_msgSendSuper(struct objc_super * _Nonnull super, SEL _Nonnull op, ...)super
objc_super 结构体指针
op
selector
objc_super
代码
修改父类参数
我们将父类修改成它自己的类对象试试。 ry_super.super_class = [RYSubModel class];,输出依然正确。内部有个查找的链条。
1.4 源码
我们找一下 objc_msgSend 的源码。
ARM64版的源码都在 objc-msg-arm64.s 中,是 汇编 的。
通过添加符号断点也可以查看到汇编代码。
二、汇编说明
2.1 这里用到的主要的的汇编指令备注
cmp
cmp x0, #0x0
作差值计算,结果丢弃,只调整状态寄存器CPSR标志位
b.le
b.le 0x1afad2b98;
意为less than or equal to,结果<=0,的时候跳转指令 0x1afad2b98
b.eq
b.eq 0x1afad2b68
意为equal,结果==0,时跳转指令0x1afad2b68
and
and x16, x13
x16 & x13 与操作
ldr
ldr x13, [x0]
将地址x0的储存器的数据,放入x13寄存器
lsr
lsr #48
逻辑右移48位
lsl
lsl #4
逻辑左移4位
ldp
ldp x17, x9, [x12]
取出x12的值存入x17,x9
b.ne
b.ne 0x1afad2b54
不相等,跳转指令0x1afad2b54
tbnz
tbnz p11, #0, LLookupPreopt\Function
Test and branch Not zero,即:如果p11的第0位不为0就执行LLookupPreopt\Function
TBZ
-
为0,和tbnz类似
adrp
adrp x0, 1
1. 将1的值,左移12位 1 0000 0000 0000 == 0x1000;\n 2.将PC寄存器的低12位清零 0x1045228b0 ==> 0x104522000; 3.将将1 和 2 的结果相加 给 X0 寄存器
eor
-
异或
2.2 寄存器说明
x0-x7,是用来传参的。x0:消息接收者
x1:sel
☕️ 休息一下吧,后面开始会有点累
三 _objc_msgSend 源码汇编
3.1 GetClassFromIsa_p16
获取Class存入p16
3.2 ExtractISA 获取ClassISA
3.3 CacheLookup 查找缓存
参数:
Mode:缓存命中模式
CacheHit
Function
命中缓存后调用
MissLabelDynamic
未命中调用
MissLabelConstant
MissLabelConstant is only used for the GETIMP variant
官方注释
建议查看一下,有助于理解
源码
源码较长,我们分部分看
3.4 LLookupStart\Function 开始查找
获取当前 bucket_t 和 HashIndex,用来查找缓存
这里对源码的
if-else缩进进行调整,方便查看
3.5 循环遍历(上)
3.6 CacheHit
3.7 TailCallCachedImp 执行缓存IMP
3.8 循环遍历(下)
当循环回到第一个bucket的时候就停止。
将p13移到 buckets + (mask << 1+PTRSHIFT)
继续遍历,直到遍历完
如果仍然没有命中,_objc_msgSend_uncached
3.9 LLookupPreopt\Function: 查找共享缓存内的数据
3.10 GetTaggedClass
if SUPPORT_TAGGED_POINTERS
3.11 LNilOrTagged
3.12 LReturnZero
☕️ 看到这里,再休息一下吧。真机汇编看着更带劲儿~
四、真机汇编调试
个人觉得看符号断点的汇编更直观,但是源码中有注释。
真机符号断点的作为参考,和源码对照着再看一遍会更加清晰。
🎉 感谢能耐心看到这里,不错不错
看汇编是真的费劲儿
objc_msgSend 汇编流程图

五、未命中缓存的处理 __objc_msgSend_uncached
这里会调用 MethodTableLookup。
5.1 查找方法列表 MethodTableLookup
5.2 慢速查找 _lookUpImpOrForward
id inst
receiver(x0)
SEL sel
selector(x1)
Class cls
ClassISA
int behavior
method lookup
behavior:
LOOKUP_INITIALIZE | LOOKUP_RESOLVER)
流程图

源码解析
5.3 二分查找赏析
这里的算法比较精妙,值得学习。
六、动态决议与消息转发
附:知识点补充
1. 取 buckets 过程的补充说明
这里的第0位,是指低位的0位
2. OBJC2
我们在进行源码学习的时候时常会见到__OBJC2__这个宏定义。
因为
runtime有两个版本1.0:用于早期系统上,32位的MacOS上
2.0(现行版本):
iOS和MacOS X 10.5以后的64位系统上
想要了解更多可以查看下面的官方文档:
3. cache_t
4. Class的实现(Realize)和初始化(Initialize)
Last updated
Was this helpful?