在Cache_t的结构和原理一文中,我们通过insert
函数来分析了cache
的实现原理。沿着cache_t::insert(Class cls, SEL sel, IMP imp, id receiver)
这条主线我们来探索,在insert
之前发生了什么,通过查看源码我们找到了void cache_fill(Class cls, SEL sel, IMP imp, id receiver)
方法,在cache_fill
之前是什么操作呢?
* objc_msgSend // 1
* cache_getImp // 2
*
* Cache writers (hold cacheUpdateLock while reading or writing; not PC-checked)
* cache_fill (acquires lock) // 3
* cache_expand (only called from cache_fill) // 4
* cache_create (only called from cache_expand)
* bcopy (only called from instrumented cache_expand)
* flush_caches (acquires lock)
* cache_flush (only called from cache_fill and flush_caches)
* cache_collect_free (only called from cache_expand and cache_flu
- 1,函数调用会转变为
objc_msgSend
函数。 - 2,从
cache_t
里面查找该方法实现。 - 3,向
cache_t
里面插入新的bucket(_sel, _imp)
- 4,如果
_occupied
已达到阈值
,则进行扩容。
我们今天就一起来探索objc_msg_send
的方法快速查找。
运行时
代码程序按照执行时机有两种状态编译时
和运行时
。
编译时
:就是编译器
把源代码
翻译成及其能识别的代码。主要是做一些简单的翻译工作
。
- 1,会进行
静态类型检查
。 - 2,在
预编译
时,会展开所有的宏定义
。 - 3,进行
语法分析
,语义分析
等等。
运行时
:就是将代码跑起来,已经被装载到内存中去了。
iOS 中,runtime
有俩个版本,一个是Legacy版本
,一个是Modern版本(现行版本)
。

如上图所示,
OC代码
会经过llvm(编译器)
,将代码转化为runtime System Library
能识别的方法。
消息发送初步探索
我们将如下代码通过clang
转化为c++
LGPerson *person = [LGPerson alloc];
[person sayHello];
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc -fobjc-arc -fobjc-runtime=ios-8.0.0 main.m -o main.cpp
上面的代码还转化为
LGPerson *person = ((LGPerson *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("LGPerson"), sel_registerName("alloc"));
((void (*)(id, SEL))(void *)objc_msgSend)((id)person, sel_registerName("sayHello"));
上面C++代码可简化为
LGPerson *person = objc_msgSend(objc_getClass("LGPerson"), sel_registerName("alloc"));
objc_msgSend((id)person, sel_registerName("sayHello"));
从如上代码我们可以看出,sayHello
该方法调用,转化为可objc_msgSend
函数调用。那objc_msgSend
是怎么来发送消息呢,我们来进行下一步探讨。
objc_msgSend
源码中关于objc_msgSend
的实现是一段汇编代码
ENTRY _objc_msgLookup
UNWIND _objc_msgLookup, NoFrame
cmp p0, #0 // nil check and tagged pointer check
#if SUPPORT_TAGGED_POINTERS
b.le LLookup_NilOrTagged // (MSB tagged pointer looks negative)
#else
b.eq LLookup_Nil
#endif
ldr p13, [x0] // p13 = isa
GetClassFromIsa_p16 p13 // p16 = class // 1
LLookup_GetIsaDone:
// returns imp
CacheLookup LOOKUP, _objc_msgLookup // 2
- 1,通过
isa
信息,来得到类的信息
,在isa的结构
一文中,我们可以得到,将isa & ISA_MASK
就可以得到类信息
。 - 2,
CacheLookup
在cache_t
中查找是否有该方法。
下面我们来着重分析 CacheLookup
:
CacheLookup
在Cache_t的结构和原理一文中,我们讲解了 cache
是如何存储的,接下来,我们来分析它是如何查找
的?
cacheLookup
的实现如下所示:
.macro CacheLookup
LLookupStart$1:
// p1 = SEL, p16 = isa
ldr p11, [x16, #CACHE] // p11 = mask|buckets
#if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16
and p10, p11, #0x0000ffffffffffff // p10 = buckets
and p12, p1, p11, LSR #48 // x12 = _cmd & mask
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_LOW_4
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
add p12, p10, p12, LSL #(1+PTRSHIFT)
// p12 = buckets + ((_cmd & mask) << (1+PTRSHIFT))
ldp p17, p9, [x12] // {imp, sel} = *bucket
1: cmp p9, p1 // if (bucket->sel != _cmd)
b.ne 2f // scan more
CacheHit $0 // call or return imp
2: // not hit: p12 = not-hit bucket
CheckMiss $0 // miss if bucket->sel == 0
cmp p12, p10 // wrap if bucket == buckets
b.eq 3f
ldp p17, p9, [x12, #-BUCKET_SIZE]! // {imp, sel} = *--bucket
b 1b // loop
3: // wrap: p12 = first bucket, w11 = mask
#if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16
add p12, p12, p11, LSR #(48 - (1+PTRSHIFT))
// p12 = buckets + (mask << 1+PTRSHIFT)
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_LOW_4
add p12, p12, p11, LSL #(1+PTRSHIFT)
// p12 = buckets + (mask << 1+PTRSHIFT)
#else
#error Unsupported cache mask storage for ARM64.
#endif
// Clone scanning loop to miss instead of hang when cache is corrupt.
// The slow path may detect any corruption and halt later.
ldp p17, p9, [x12] // {imp, sel} = *bucket
1: cmp p9, p1 // if (bucket->sel != _cmd)
b.ne 2f // scan more
CacheHit $0 // call or return imp
2: // not hit: p12 = not-hit bucket
CheckMiss $0 // miss if bucket->sel == 0
cmp p12, p10 // wrap if bucket == buckets
b.eq 3f
ldp p17, p9, [x12, #-BUCKET_SIZE]! // {imp, sel} = *--bucket
b 1b // loop
LLookupEnd$1:
LLookupRecover$1:
3: // double wrap
JumpMiss $0
.endmacro
其大致流程如下
- 1,将
sel
与mask
进行与运算
,得到一个索引
值, - 2,如果在
buckets
中,直接命中(_imp == 查找的imp)
,则返回对应的_imp
。 - 3,如果 在
buckets
中,直接命中且_imp != 查找的imp
,则在buckets
的尾部
开始查找该_imp
。 - 4,如果在
buckets
中没有找到该_imp
,则返回nil
,会调用objc_msgSend_uncached
方法。
总结
在这篇文章里,我们探索了探索了objc_msgSend
里面的快速查找
流程,主要是从cache_t
缓存的方法中进行查找。
网友评论