消息传递转发首先肯定要了解个知识点 runtime
官方文档 Objective-C Runtime Programming Guide
runtime官方文档Runtime
runtime
简称运行时
, 是Objective-C语言中非常重要的概念, 它不仅使得OC语言正常运行, 还使得OC语言具有动态的特性。Runtime的运行机制使得OC能够在运行时动态创建类和对象, 进行消息传递转发等
运行时, 编译时区别
-
编译时: 其实就是
正在编译的时候
, 编译器帮你把源代码翻译成机器代码的过程。 主要是对语言进行最基本的检查报错(词法分析
,语法分析
等)。例如: 编译代码时候如果有error, warning信息, 都是编译器检查出来的, 这种错误叫编译时错误
, 这个过程叫编译时类型检查
或者静态类型检查
。留意: 编译时只是把代码当做文本扫描下, 并未分配内存运行
-
运行时: 是
代码跑起来, 被装载到内存中
的过程(内存中判断), 是一个动态
阶段。
例子:
运行时/编译时例子没有sayHello实现方法, 编译时
并无报错, 而运行时
可看到由于找不到方法而报错, 这个例子也可以看出运行与编译时的区别
Runtime调用三种途径
runtime调用三种途径-
通过OC代码,例如:
[test sayNB]
(Objective-C code) -
通过NSObject方法,例如:
isKindOfClass
,isMemberOf
(Framework & Service 接口引入) -
通过Runtime API,例如:
class_getInstanceSize
(Runtime API)
compiler
就是我们了解的编译器, 那一层为编译层即LLVM
runtime system libarary
就是底层库
方法本质
为了查看一下消息流程本质, 我们Clang
一下
clang -rewrite-objc main.m -o main.cpp
cpp代码
其实我们之前也看到过, 方法的本质就是objc_msgSend
消息发送
验证一下,通过 objc_msgSend
方法来完成[person sayNB]的调用,查看其打印是否是一致
实现准备:
1、调用objc_msgSend
,需要导入头文件#import <objc/message.h>
2、需要将 target → Build Setting → msg → enable strict checking of obc_msgSend calls
由YES
改为NO
,将严厉的检查机制关掉,否则objc_msgSend的参数会报错
TestObj *test = [TestObj alloc];
[test sayNB];
objc_msgSend(test, sel_registerName("sayNB"));
// 方法在底层是消息
// 消息: 1.消息接受者 2. 消息主体
这里通过 objc_msgSend
调用方法
enable strict checking of obc_msgSend calls
没改的话会报这个错误
可看到两者一致, 即[test sayNB]
等同于objc_msgSend(test, sel_registerName("sayNB"));
调用父类方法
我们还可以模拟下调用父类方法, 具体通过objc_msgSendSuper
实现
父类SAPerson
定义方法sayHello
, 子类 SATeacher
继承SAPerson
, 不设置任何方法
objc_msgSendSuper
先看下底层
OBJC_EXPORT id _Nullable
objc_msgSendSuper(struct objc_super * _Nonnull super, SEL _Nonnull op, ...)
OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0);
#endif
方法中有两个参数(结构体struct objc_super
,方法名sel
),其中sel方法名之前讲过, 而结构体类型objc_super
看下源码
/// Specifies the superclass of an instance.
struct objc_super {
/// Specifies an instance of a class.
__unsafe_unretained _Nonnull id receiver;
/// Specifies the particular superclass of the instance to message.
#if !defined(__cplusplus) && !__OBJC2__
/* For compatibility with old objc-runtime.h header */
__unsafe_unretained _Nonnull Class class;
#else
__unsafe_unretained _Nonnull Class super_class;
#endif
/* super_class is the first class to search */
};
#endif
objc_super
结构体可看出,需要指定接收者receiver
和父类 super_class
两个属性
上面例子我们也可以看出, 子类调用父类方法[teacher sayHello]
与 objc_msgSendSuper(&s, sel_registerName("sayHello"));
都执行了父类的中sayHello
方法,我们有些疑问 是不是方法调用,首先是在类中查找,如果类中没有找到,会到类的父类中查找?
。怎么通过方法名Sel 找到 接下来我们探索下这个问题
objc_msgSend 快速查找流程分析
objc4源码中,搜索objc_msgSend
,这里留意下objc_msgSend
是汇编写的, 而不是C/C++写的。首先所有代码都会被翻译成底层汇编, 然后通过编译时
翻译成机器识别语言。
汇编语言有个特性:
-
快
。举个例子: 如果App 1s内有成千上万个方法调用, 汇编语言比用C/C++写少耗时0.01s, 也是会省下很多时间。 -
动态性
(不确定性): C/C++相对于汇编来说构造更加静态, 可能不满足消息发送很多情况
其实如果用C/C++写消息发送其实也是可以的, 不过相对麻烦, 浪费性能, 所以消息传递这一层面苹果使用汇编写的
objc_msgSend
有一个消息接受者
, 消息接受者
才能找到你真正的寻根路径
。由于方法
存在于类/元类
里面, 方法查找需要ISA
, ISA
又存在对象
里面无论是实例对象
还是类对象
。即有: 对象
→ ISA
→ 方法(类)
→ cache(是否有缓存) → methodlist(无缓存, 存在bits中)
由于我们日常开发的都是架构是arm64(手机端),所以需要在arm64.s后缀的文件中查找objc_msgSend
源码实现
// ---- 消息转发 ---- objc_msgSend主要是拿到接收者的isa信息
// _objc_msgSend 的 汇编入口
ENTRY _objc_msgSend
// 无窗口
UNWIND _objc_msgSend, NoFrame
// p0其实是objc_msgSend的第一个参数-消息接收者receiver
cmp p0, #0 // nil check and tagged pointer check
// 判空操作, p0与空作对比, 判断接受者是否存在
// 是否支持taggedpointer(小对象类型)的流程
#if SUPPORT_TAGGED_POINTERS
b.le LNilOrTagged // (MSB tagged pointer looks negative)
#else
// p0 等于 0 返回 空
b.eq LReturnZero
#endif
// p0非空, 即走接受者存在流程
// 根据对象取isa(即从x0寄存器指向的地址 取出 isa),其中代码已经提示 p13 = isa
ldr p13, [x0] // p13 = isa
// 通过 p16 = isa(p13) & ISA_MASK,拿出shiftcls信息,得到class信息
// GetClassFromIsa_p16 详细源码在下方
GetClassFromIsa_p16 p13, 1, x0 // p16 = class
LGetIsaDone:
// calls imp or objc_msgSend_uncached
// 如果有isa,走CacheLookup (即缓存查找流程),也就是sel-imp快速查找
CacheLookup NORMAL, _objc_msgSend, __objc_msgSend_uncached
#if SUPPORT_TAGGED_POINTERS
// 下边是小对象处理流程
// 如果是空返回nil, 不为空直接走下面的操作
LNilOrTagged:
b.eq LReturnZero // nil check
GetTaggedClass
b LGetIsaDone
// SUPPORT_TAGGED_POINTERS
#endif
LReturnZero:
// x0 is already zero
mov x1, #0
movi d0, #0
movi d1, #0
movi d2, #0
movi d3, #0
ret
END_ENTRY _objc_msgSend
.macro GetClassFromIsa_p16 src, needs_auth, auth_address /* note: auth_address is not required if !needs_auth */
#if SUPPORT_INDEXED_ISA
// Indexed isa
// 将isa的值存入p16寄存器
mov p16, \src // optimistically set dst = src
// 判断是否是 nonpointer isa
tbz p16, #ISA_INDEX_IS_NPI_BIT, 1f // done if not non-pointer isa
// isa in p16 is indexed
// 将_objc_indexed_classes所在的基址 读入x10寄存器
adrp x10, _objc_indexed_classes@PAGE
// x10 = x10 + _objc_indexed_classes(page中的偏移量), x10基址 根据 偏移量 进行 内存偏移
add x10, x10, _objc_indexed_classes@PAGEOFF
// 从p16的第ISA_INDEX_SHIFT位开始,提取 ISA_INDEX_BITS 位到p16寄存器,剩余的高位用0补充
ubfx p16, p16, #ISA_INDEX_SHIFT, #ISA_INDEX_BITS // extract index
ldr p16, [x10, p16, UXTP #PTRSHIFT] // load class from array
1:
// 用于64位系统
#elif __LP64__
.if \needs_auth == 0 // _cache_getImp takes an authed class already
mov p16, \src
.else
// 64-bit packed isa
ExtractISA p16, \src, \auth_address
.endif
#else
// 用于32位系统
// 32-bit raw isa
mov p16, \src
#endif
.endmacro
大致可分为以下几步
第一步
判断objc_msgSend
方法的第一个参数接收者receiver
是否为空, 不为空继续往下走
-
如果支持
tagged pointer
(小对象类型), 跳转LNilOrTagged
- 如果
小对象
为空, 则返回空(LReturnZero
) - 如果
小对象
不为空, 则找到小对象的isa
, 接着走下一步
- 如果
-
如果即不是
tagged pointer
(小对象类型), receiver也不为空, 则- 从
receiver
中取出isa
存入p13
寄存器 - 通过 GetClassFromIsa_p16中,arm64架构下通过 isa & ISA_MASK 获取shiftcls位域的类信息,接着走下一步
- 从
第二步
获取完类信息完毕,进入慢速查找流程CacheLookup NORMAL
, 看下CacheLookup 源码
#if defined(__arm64__) && __LP64__
#if TARGET_OS_OSX || TARGET_OS_SIMULATOR
#define CACHE_MASK_STORAGE CACHE_MASK_STORAGE_HIGH_16_BIG_ADDRS
#else
#define CACHE_MASK_STORAGE CACHE_MASK_STORAGE_HIGH_16
#endif
#elif defined(__arm64__) && !__LP64__
#define CACHE_MASK_STORAGE CACHE_MASK_STORAGE_LOW_4
#else
#define CACHE_MASK_STORAGE CACHE_MASK_STORAGE_OUTLINED
#endif
.macro CacheLookup Mode, Function, MissLabelDynamic, MissLabelConstant
//
// Restart protocol:
//
// As soon as we're past the LLookupStart\Function label we may have
// loaded an invalid cache pointer or mask.
//
// When task_restartable_ranges_synchronize() is called,
// (or when a signal hits us) before we're past LLookupEnd\Function,
// then our PC will be reset to LLookupRecover\Function which forcefully
// jumps to the cache-miss codepath which have the following
// requirements:
//
// GETIMP:
// The cache-miss is just returning NULL (setting x0 to 0)
//
// NORMAL and LOOKUP:
// - x0 contains the receiver
// - x1 contains the selector
// - x16 contains the isa
// - other registers are set as per calling conventions
//
mov x15, x16 // stash the original isa
LLookupStart\Function:
// p1 = SEL, p16 = isa
// #define CACHE (2 * __SIZEOF_POINTER__), 其中 __SIZEOF_POINTER__表示pointer的大小 ,即 2*8 = 16
// OSX 系统 或者 64位模拟器
#if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16_BIG_ADDRS
ldr p10, [x16, #CACHE] // p10 = mask|buckets
// p10 (cache) = mask|buckets 从x16(isa)中平移16字节得到, 存到p10
// 接下来, 取出mask, 取出buckets
// p10(cache) 右移48位得到p11, p11为mask
// p10(cache) & 0x0000ffffffffffff , 之后 p10为buckets
// 这里其实是mask高16位抹零,得到buckets 存入p10寄存器, 即去掉mask,留下buckets
// x12为_cmd & mask
lsr p11, p10, #48 // p11 = mask
and p10, p10, #0xffffffffffff // p10 = buckets
and w12, w1, w11 // x12 = _cmd & mask
// 其余arm架构 64位机器, 大部分方法与上面类似
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16
// 真机留意下p11为cache = mask|buckets
// 方式照旧 x16(isa)中平移16字节,取出cache 存入p11寄存器
// isa距离cache 正好16字节:isa(8字节)-superClass(8字节)-cache(mask高16位 + buckets低48位)
ldr p11, [x16, #CACHE] // p11 = mask|buckets
// 如果配置使用了缓存, 走下边
#if CONFIG_USE_PREOPT_CACHES
// p11(cache) & 0x0000ffffffffffff , 之后 得到buckets 存入寄存器p10
// 跟上面一样, mask高16位抹零,得到buckets 存入p10寄存器, 即去掉mask,留下buckets
// 下面这里判断是否是真机还是其他系统, 区别仅仅顺序不一样, 都是 p10 = buckets操作
#if __has_feature(ptrauth_calls)
tbnz p11, #0, LLookupPreopt\Function
and p10, p11, #0x0000ffffffffffff // p10 = buckets
#else
and p10, p11, #0x0000fffffffffffe // p10 = buckets
tbnz p11, #0, LLookupPreopt\Function
// 新版的cache取值同样做了一个判断前置是否有cache操作
// #if CONFIG_USE_PREOPT_CACHES
// 如果有就平移7位做个异或操作
// value ^= value >> 7;
// #endif
// return (mask_t)(value & mask);
// 下面类似, 其实就是 x12 = _cmd & mask
// p11(cache)右移48位,得到mask(即p11 存储mask), mask & p1(msgSend的第二个参数 cmd-sel) ,得到sel-imp的下标index(即搜索下标) 存入p12(cache insert写入时的哈希下标计算是 通过 sel & mask,读取时也需要通过这种方式)
eor p12, p1, p1, LSR #7
and p12, p12, p11, LSR #48 // x12 = (_cmd ^ (_cmd >> 7)) & mask
#else
and p10, p11, #0x0000ffffffffffff // p10 = buckets
and p12, p1, p11, LSR #48 // x12 = _cmd & mask
#endif // CONFIG_USE_PREOPT_CACHES
// 非64位真机
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_LOW_4
ldr p11, [x16, #CACHE] // p11 = mask|buckets
and p10, p11, #~0xf // p10 = buckets
and p11, p11, #0xf // p11 = maskShift
mov p12, #0xffff
lsr p11, p12, p11 // p11 = mask = 0xffff >> p11
and p12, p1, p11 // x12 = _cmd & mask
#else
#error Unsupported cache mask storage for ARM64.
#endif
// p13是下标 p10是buckets数组首地址,下标 偏移16 得到实际内存的偏移量,通过buckets的首地址偏移,获取bucket存入p13寄存器
// PTRSHIFT是3, 1+3 = 4, 1<<4 即 偏移2^4 = 16
// LSL #(1+PTRSHIFT)-- 实际含义就是得到一个bucket占用的内存大小 -- 相当于mask = occupied -1-- _cmd & mask -- 取余数
add p13, p10, p12, LSL #(1+PTRSHIFT)
// p13 = buckets + ((_cmd & mask) << (1+PTRSHIFT))
// do {
// 从x13(即p13)中取出 bucket 分别将imp和sel 存入 p17(存储imp) 和 p9(存储sel)
1: ldp p17, p9, [x13], #-BUCKET_SIZE // {imp, sel} = *bucket--
// 比较查询的sel 与 p1(传入的参数cmd)是否相同
cmp p9, p1 // if (sel != _cmd) {
// 不相等跳转3f
b.ne 3f // scan more
// } else {
// 如果相等, 即找到查询的方法, cacheHit 缓存命中,直接返回imp
2: CacheHit \Mode // hit: call or return imp
// }
// 如果sel == 0, 走miss
3: cbz p9, \MissLabelDynamic // if (sel == 0) goto Miss;
// 判断p13(下标对应的bucket) 是否 等于 p10(buckets数组第一个元素)
cmp p13, p10 // } while (bucket >= buckets)
// bucket >= buckets 回到1b循环操作
b.hs 1b
// wrap-around:
// p10 = first bucket
// p11 = mask (and maybe other bits on LP64)
// p12 = _cmd & mask
//
// A full cache can happen with CACHE_ALLOW_FULL_UTILIZATION.
// So stop when we circle back to the first probed bucket
// rather than when hitting the first bucket again.
//
// Note that we might probe the initial bucket twice
// when the first probed slot is the last entry.
// 令 p13 = buckets + (mask << 1+PTRSHIFT)
#if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16_BIG_ADDRS
add p13, p10, w11, UXTW #(1+PTRSHIFT)
// p13 = buckets + (mask << 1+PTRSHIFT)
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16
add p13, p10, p11, LSR #(48 - (1+PTRSHIFT))
// p13 = buckets + (mask << 1+PTRSHIFT)
// see comment about maskZeroBits
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_LOW_4
add p13, p10, p11, LSL #(1+PTRSHIFT)
// p13 = buckets + (mask << 1+PTRSHIFT)
#else
#error Unsupported cache mask storage for ARM64.
#endif
add p12, p10, p12, LSL #(1+PTRSHIFT)
// p12 = first probed bucket
// do {
// 向前查找 {imp, sel} = *bucket--
4: ldp p17, p9, [x13], #-BUCKET_SIZE // {imp, sel} = *bucket--
// 再次比较 `sel(p9)` 与 `cmd(p1)`是否相等
cmp p9, p1 // if (sel == _cmd)
// 相等, 走2b, 即找到查询的方法, `cacheHit` 缓存命中,直接返回`imp`
b.eq 2b // goto hit
// 如果while (sel != 0 &&bucket > first_probed) 循环走4b方法
cmp p9, #0 // } while (sel != 0 &&
ccmp p13, p12, #0, ne // bucket > first_probed)
b.hi 4b
LLookupEnd\Function:
LLookupRecover\Function:
b \MissLabelDynamic
#if CONFIG_USE_PREOPT_CACHES
#if CACHE_MASK_STORAGE != CACHE_MASK_STORAGE_HIGH_16
#error config unsupported
#endif
LLookupPreopt\Function:
#if __has_feature(ptrauth_calls)
and p10, p11, #0x007ffffffffffffe // p10 = buckets
autdb x10, x16 // auth as early as possible
#endif
// x12 = (_cmd - first_shared_cache_sel)
adrp x9, _MagicSelRef@PAGE
ldr p9, [x9, _MagicSelRef@PAGEOFF]
sub p12, p1, p9
// w9 = ((_cmd - first_shared_cache_sel) >> hash_shift & hash_mask)
#if __has_feature(ptrauth_calls)
// bits 63..60 of x11 are the number of bits in hash_mask
// bits 59..55 of x11 is hash_shift
lsr x17, x11, #55 // w17 = (hash_shift, ...)
lsr w9, w12, w17 // >>= shift
lsr x17, x11, #60 // w17 = mask_bits
mov x11, #0x7fff
lsr x11, x11, x17 // p11 = mask (0x7fff >> mask_bits)
and x9, x9, x11 // &= mask
#else
// bits 63..53 of x11 is hash_mask
// bits 52..48 of x11 is hash_shift
lsr x17, x11, #48 // w17 = (hash_shift, hash_mask)
lsr w9, w12, w17 // >>= shift
and x9, x9, x11, LSR #53 // &= mask
#endif
ldr x17, [x10, x9, LSL #3] // x17 == sel_offs | (imp_offs << 32)
cmp x12, w17, uxtw
.if \Mode == GETIMP
b.ne \MissLabelConstant // cache miss
sub x0, x16, x17, LSR #32 // imp = isa - imp_offs
SignAsImp x0
ret
.else
b.ne 5f // cache miss
sub x17, x16, x17, LSR #32 // imp = isa - imp_offs
.if \Mode == NORMAL
br x17
.elseif \Mode == LOOKUP
orr x16, x16, #3 // for instrumentation, note that we hit a constant cache
SignAsImp x17
ret
.else
.abort unhandled mode \Mode
.endif
5: ldursw x9, [x10, #-8] // offset -8 is the fallback offset
add x16, x16, x9 // compute the fallback isa
b LLookupStart\Function // lookup again with a new isa
.endif
#endif // CONFIG_USE_PREOPT_CACHES
.endmacro
第三步
isa
首地址平移16
字节取cache
, 因为在objc_class
中, cache与首地址距离正好相差16
字节( isa
占8
字节,superclass
占8
字节。
第四步
因为cache
中高16
位存mask
,低48
位存buckets
,即p11 = cache = mask | buckets
。从cache
中取出buckets
和mask
,并由mask
根据哈希算法计算出哈希下标
-
cache
和掩码(0x0000ffffffffffff)
做&
运算,将高16位mask抹零,得到buckets指针地址,即p10 = buckets -
将
cache
右移48
位,得到mask
,即p11 = mask
-
将
objc_msgSend
的参数p1
(即第二个参数_cmd
)&mask
,通过哈希算法,得到需要查找存储sel-imp
的bucket
下标index
,即p12 = index = _cmd & mask
。因为在存储sel-imp
时,也是通过同样哈希算法计算哈希下标进行存储,所以读取也需要通过同样的方式读取
下面是mask_t cache_hash
源码
#if defined(__arm64__) && TARGET_OS_IOS && !TARGET_OS_SIMULATOR && !TARGET_OS_MACCATALYST
#define CONFIG_USE_PREOPT_CACHES 1
#else
#define CONFIG_USE_PREOPT_CACHES 0
#endif
static inline mask_t cache_hash(SEL sel, mask_t mask)
{
uintptr_t value = (uintptr_t)sel;
#if CONFIG_USE_PREOPT_CACHES
value ^= value >> 7;
#endif
return (mask_t)(value & mask);
}
第五步
根据所得的哈希下标index
和 buckets首地址
,取出哈希下标对应的bucket, 目的是为了进而找到sel
和imp
。
结构体bucket_t
中sel
占8
字节,imp
占8
字节
通过首地址 + 实际偏移量,获取哈希下标index对应的bucket
第六步
根据获取的bucket, 取出imp
, sel
, 令p17(存储imp)
和 p9(存储sel)
第七步
-
比较
bucket
中sel(p9)
与cmd(p1)
是否相等-
相等跳转
2b
, 即找到查询的方法,cacheHit
缓存命中,直接返回imp
-
不相等跳转
3f
①sel == 0
:goto miss
, 走miss
方法, 跳转至objc_msgSend_uncached
即慢速查找流程
② 相等, 判断p13(下标对应的bucket) 是否 等于 p10(buckets数组第一个元素)
// p10 = first bucket③ 如果
bucket
> =buckets
的第一个元素,回到1b
进行递归循环对比 if (sel != _cmd)④ 如果
bucket
<buckets
, 往下走
-
-
p13 = buckets + (mask << 1+PTRSHIFT)
-
先做向前查找
{imp, sel} = *bucket--
, 再次比较sel(p9)
与cmd(p1)
是否相等- 相等, 走2b, 即找到查询的方法,
cacheHit
缓存命中,直接返回imp
- 不相等 如果while (sel != 0 &&bucket > first_probed) 循环走4b方法
- 相等, 走2b, 即找到查询的方法,
总结
由于源码比较复杂, 接下来我们模拟下快速查找
// 获取当前对象
id person = 0x10000 //随意举个例子
// 获取当前isa
isa_t isa = 0x000000
// 获取当前class isa → class → cache
// 因为isa才能获取class, 进而获取cache
cache_t cache = isa + 16字节
// 进入 CacheLookup 方法
// 由于arm64下
cache = mask|buckets
// buckets 可由cache & 掩码0x0000ffffffffffff 得到
buckets = cache & 0x0000ffffffffffff
// mask 可由cache右移48位得到
mask = cache lsr #48
// 得到需要查找存储sel imp的bucket下标index, index = _cmd(传入的参数p1) & mask
index = _cmd & mask / (_cmd ^ (_cmd >> 7)) & mask
// 举个例子: person 有个sayHello方法, 那么我们要找到sayHello, 需要先找到bucket, 因为bucket里面才有sel和imp
[person sayHello] → imp (cache → bucket → (sel imp))
// 从buckets中遍历查找对应下标的bucket值 (sel + imp = 16, 即下标多少个就平移多少个bucket)
bucket = buckets + ((_cmd & mask) << (1+PTRSHIFT))
= buckets + index * 16
//// 判断bucket里面的sel, 是否匹配cmd
//if (sel == 0) {
// // 走miss方法, 跳转至objc_msgSend_uncached即慢速查找流程
// goto miss
//}else if (bucket.sel == _cmd) {
// // 如果相等, 缓存命中
// return imp
//}else {
// // 如果不相等, 进入第二层判断
// if (bucket == buckets) {
// buckets + (mask << 1+PTRSHIFT)
// }
//}
do {
// 向前查找 {imp, sel} = *bucket-- (bucket = bucket - 1)
bucket--
// 判断bucket里面的sel, 是否匹配cmd
if (bucket.sel == _cmd) {
// 如果相等, 缓存命中
return imp
}else {
// bucket.sel != _cmd 方法
// 走miss方法, 跳转至objc_msgSend_uncached即慢速查找流程
if (bucket.sel == 0) {
goto miss
}
}
}while (bucket >= buckets)
//留意下这个相比之前781源码会做2次do while, 分离开了goto miss 以及 sel == 0 方法
// 人为设置bucket为最后一个元素
bucket = buckets + (mask << 1+PTRSHIFT)
do {
// 指针平移, 缓存查找顺序向前查找 {imp, sel} = *bucket-- (bucket = bucket - 1)
bucket--
if (bucket.sel == _cmd) {
// 如果相等, 缓存命中
// 即 sel = bucket.sel, imp = bucket.imp,
return imp
}
}while ( sel != 0 && bucket > first_probed(first probed bucket 第一个元素))
如果快速查找没有找到就开启慢速查找
网友评论