11.源码解读objc_msgSend

前言

  • 本文基于

    • objc4-818.2源码

    • iOS14.6 iPhoneX

  • 源码版本、系统版本、设备不同的话,真机调试的汇编会和源码有不同。自行调试的时候需要注意

  • 源码中对于重要部分做了注释,...表示省略了一些其他架构或者无关紧要的代码

  • 为了方便阅读,汇编代码中的做了缩进调整分块注释,阅读起来应该会轻松不少

  • 电脑端阅读体验更佳哦

  • 本文看着会比较累,用了个看着比较轻松愉悦的主题。

    • 配上一杯 ☕️ 体验更佳

  • objc_msgSend 相关的内容很多,此文会持续更新、修正

    • 具体更新内容可以查看更新记录

欢迎收藏、交流、抓虫

更新记录

更新日期
变更

20210701

首次发布

一、平时我们是如何调用方法的?

有三种方式进行调用: OC Method

NSObject API

objc API

API的层级结构如下:

runtimeLevel.png

1.1 实验三种方式

需要注意的点:

  • #import <objc/message.h>

  • Xcode 12 以下

    • Build setting -> objc_msgSend -> Enable Strict Checking of objc_msgSend Calls -> NO

  • Xcode 12 以上

    • 需要对 objc_msgSend 转类型,才能正常调用

msgsend01.png

输出:都能够正常调用

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汇编流程图.png

五、未命中缓存的处理 __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__这个宏定义。

3. cache_t

【Lawliet玩iOS源码】Class: cache_t

4. Class的实现(Realize)和初始化(Initialize)

类的实现与初始化

Last updated

Was this helpful?