找到objc_msgSend
调用方法,打断点
通过汇编发现调用
objc_msgSend
,step into
,发现objc
源码里面实现
通过
objc
源码找到objc_msgSend
的实现入口,其中不同的架构有不同的实现,这里我们主要看arm64
汇编分析
ENTRY _objc_msgSend
UNWIND _objc_msgSend, NoFrame
cmp p0, #0 // nil check and tagged pointer check
#if SUPPORT_TAGGED_POINTERS
b.le LNilOrTagged // (MSB tagged pointer looks negative)
#else
b.eq LReturnZero
#endif
ldr p13, [x0] // p13 = isa
GetClassFromIsa_p16 p13, 1, x0 // p16 = class
LGetIsaDone:
// calls imp or objc_msgSend_uncached
CacheLookup NORMAL, _objc_msgSend, __objc_msgSend_uncached
#if SUPPORT_TAGGED_POINTERS
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
- 先进入
ENTRY _objc_msgSend
方法 -
cmp p0, #0
检查对象是否为空p0
上是对象本身,cmp
比较 - 如果为空则跳转到
LReturnZero
,b.le
小于等于并跳转,b.eq
等于并跳转
4.ldr p13, [x0]
,读取x0
寄存器的值,赋值给p13
,这里的p13实际上是lisa
5.GetClassFromIsa_p16 p13, 1, x0
,通过isa 获取到对象的class
6.CacheLookup
通过缓存查找
GetClassFromIsa_p16分析
.macro GetClassFromIsa_p16 src, needs_auth, auth_address /* note: auth_address is not required if !needs_auth */
#if SUPPORT_INDEXED_ISA
// Indexed isa
...省略
#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-bit raw isa
mov p16, \src
#endif
.endmacro
INDEXED_ISA
表示直接是一个raw isa,PACKED_ISA
,需要通过一个掩码取获取,那么这段代码实际上只是调用了 ExtractISA
方法,
进入ExtractISA
#if __has_feature(ptrauth_calls)
.macro ExtractISA
and $0, $1, #ISA_MASK
#if ISA_SIGNING_AUTH_MODE == ISA_SIGNING_STRIP
xpacd $0
#elif ISA_SIGNING_AUTH_MODE == ISA_SIGNING_AUTH
mov x10, $2
movk x10, #ISA_SIGNING_DISCRIMINATOR, LSL #48
autda $0, x10
#endif
.endmacro
#else
.macro ExtractISA
and $0, $1, #ISA_MASK
.endmacro
- __has_feature(ptrauth_calls): 是判断编译器是否支持指针身份验证功能
- ptrauth_calls 指针身份验证,针对arm64e架构;使用Apple A12或更高版本A系列处理器的设备(如iPhone XS、iPhone XS Max和iPhone XR或更新的设备)支持-arm64e架构
上面的机型的判断并不影响阅读源码,最主要还是and $0, $1, #ISA_MASK
,
1表示src,也就是p13,and
是与运算,p16 = p13 & ISA_MASK
ISA_MASK
是isa里面宏定义的掩码,所以p16
就是对象的class
指针
CacheLookup分析
CacheLookup,这个方法是快速查找的核心
首先一进来会有架构的判断,我们找到真机的方法
CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16
ldr p11, [x16, #CACHE]
-
x16
位class
的指针,CACHE
的大小为16
个字节,也就是p11位cache位置的指针
#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
#endif
eor p12, p1, p1, LSR #7
and p12, p12, p11, LSR #48
- 我们以 A12为准,
tbnz p11, #0, LLookupPreopt\Function
,tbnz
:表示如果测试位不为0则跳转到LLookupPreopt
方法,LLookupPreopt
这个表示从共享缓存去加载(UIKit,Foundation),在这里不做过多的分析, -
and p10, p11, #0x0000ffffffffffff
表示p10 = p11 & 0x0000ffffffffffff
, 那么p10
表示bucket
首地址 -
eor p12, p1, p1, LSR #7
,and p12, p12, p11, LSR #48
这两句实际上通过sel
拿到bucket index
,p1
表示sel,LSR
右移,eor
异或操作,p12 ,其实是bucket的索引
add p13, p10, p12, LSL #(1+PTRSHIFT)
// p13 = buckets + ((_cmd & mask) << (1+PTRSHIFT))
// do {
1: ldp p17, p9, [x13], #-BUCKET_SIZE // {imp, sel} = *bucket--
cmp p9, p1 // if (sel != _cmd) {
b.ne 3f // scan more
// } else {
2: CacheHit \Mode // hit: call or return imp
// }
3: cbz p9, \MissLabelDynamic // if (sel == 0) goto Miss;
cmp p13, p10 // } while (bucket >= buckets)
b.hs 1b
-
add p13, p10, p12, LSL #(1+PTRSHIFT)
,p13 = p10 + p12 << 4
,
那么p13 实际上就是当前发送方法的bucket
,bucket
存储的是{imp,sel} - 将imp,sel分别保存到p17 和p9,然后遍历
buckets
去对比,如果发现有和调用的sel和buckets有相同的,就直接返回imp,也就是CacheHit
缓存命中, - 如果遍历都没有找到,即
cbz p9
位0,即sel为空,那么跳转到MissLabelDynamic,即没有找到。
用一张图总结bucket查找过程:
缓存没有找到
缓存没有找到则会进入__objc_msgSend_uncached
CacheLookup NORMAL, _objc_msgSend,__objc_msgSend_uncached
继续往下走:
STATIC_ENTRY __objc_msgSend_uncached
UNWIND __objc_msgSend_uncached, FrameWithNoSaves
// THIS IS NOT A CALLABLE C FUNCTION
// Out-of-band p15 is the class to search
MethodTableLookup
TailCallFunctionPointer x17
END_ENTRY __objc_msgSend_uncached
发现会调用MethodTableLookup
,最后会调用lookUpImpOrForward
方法,发现
lookUpImpOrForward
在汇编里面没有找到实现,发现实现在objc-runtime-new.mm里面,在此就近入方法的慢速查找流程。
lookUpImpOrForward分析
准备工作
IMP lookUpImpOrForward(id inst, SEL sel, Class cls, int behavior)
{
//准备工作
if (slowpath(!cls->isInitialized())) {
behavior |= LOOKUP_NOCACHE;
}
checkIsKnownClass(cls);
cls = realizeAndInitializeIfNeeded_locked(inst, cls, behavior & LOOKUP_INITIALIZE);
//循环查找
for (unsigned attempts = unreasonableClassCount();;){
}
return imp;
}
- cls->isInitialized() 检测类是否初始化
- checkIsKnownClass 检测类是否已经注册
- realizeAndInitializeIfNeeded_locked检测类是否已经实现,没有实现会递归去实现父类和元类
二分查找流程(重点)
循环查找
for (unsigned attempts = unreasonableClassCount();;) {
if (curClass->cache.isConstantOptimizedCache(/* strict */true)) {
#if CONFIG_USE_PREOPT_CACHES
imp = cache_getImp(curClass, sel);
if (imp) goto done_unlock;
curClass = curClass->cache.preoptFallbackClass();
#endif
} else {
// curClass method list.
Method meth = getMethodNoSuper_nolock(curClass, sel);
if (meth) {
imp = meth->imp(false);
goto done;
}
if (slowpath((curClass = curClass->getSuperclass()) == nil)) {
// No implementation found, and method resolver didn't help.
// Use forwarding.
imp = forward_imp;
break;
}
}
// Halt if there is a cycle in the superclass chain.
if (slowpath(--attempts == 0)) {
_objc_fatal("Memory corruption in class list.");
}
// Superclass cache.
imp = cache_getImp(curClass, sel);
if (slowpath(imp == forward_imp)) {
// Found a forward:: entry in a superclass.
// Stop searching, but don't cache yet; call method
// resolver for this class first.
break;
}
if (fastpath(imp)) {
// Found the method in a superclass. Cache it in this class.
goto done;
}
}
-
cache.isConstantOptimizedCache
先从缓存中判断是否存在,如果存在直接从缓存中取 -
getMethodNoSuper_nolock
,如果没有找到从类的methods里面取找
getMethodNoSuper_nolock分析
for (auto mlists = methods.beginLists(),
end = methods.endLists();
mlists != end;
++mlists)
{
// getMethodNoSuper_nolock is the hottest
// caller of search_method_list, inlining it turns
// getMethodNoSuper_nolock into a frame-less function and eliminates
// any store from this codepath.
method_t *m = search_method_list_inline(*mlists, sel);
if (m) return m;
}
- 可以看出
methods
是一个二维数字,methods-> mlists
- 真正的查找是在
mlists
里面
继续进入search_method_list_inline
在这个里面有两个方法,
-
findMethodInSortedMethodList
已经拍好序的 -
findMethodInUnsortedMethodList
没有排好序的
我们知道如果要进行二分查找,数组必须有序,如果是没有排序的直接遍历数组去找。
没有排序,循环遍历
findMethodInUnsortedMethodList(SEL sel, const method_list_t *list, const getNameFunc &getName)
{
for (auto& meth : *list) {
if (getName(meth) == sel) return &meth;
}
return nil;
}
排序的,二分查找:
findMethodInSortedMethodList(SEL key, const method_list_t *list, const getNameFunc &getName)
{
ASSERT(list);
auto first = list->begin();
auto base = first;
decltype(first) probe;
uintptr_t keyValue = (uintptr_t)key;
uint32_t count;
for (count = list->count; count != 0; count >>= 1) {
probe = base + (count >> 1);
uintptr_t probeValue = (uintptr_t)getName(probe);
if (keyValue == probeValue) {
// `probe` is a match.
// Rewind looking for the *first* occurrence of this value.
// This is required for correct category overrides.
while (probe > first && keyValue == (uintptr_t)getName((probe - 1))) {
probe--;
}
return &*probe;
}
if (keyValue > probeValue) {
base = probe + 1;
count--;
}
}
return nil;
}
- 苹果的工程是用的很巧妙,通过右移来实现折半查找
- 这里有一个主意的点,就是我们发现即使找到了还有一个循环,
probe--
,这里实际上是因为如果有分类的方法和类的方法一样的话,会优先使用分类的方法,This is required for correct category overrides.
注释里面也给出了解释
如果自身的类没有找到,就会查找父类,一值到跟类。
if (slowpath((curClass = curClass->getSuperclass()) == nil)) {
// No implementation found, and method resolver didn't help.
// Use forwarding.
imp = forward_imp;
break;
}
如果都没有找到就会走消息转发forward_imp
总结
上面就是方法的二分查找,其实消息的发送还没有结束,如果自身类,父类,根类(NSObject
)都没有找到就会走转发流程也就是forward_imp
补充(initialize)
还有一个关于initialize方法的调用,我们再调用其他方法是也可能会走initialize
方法,这个方法在我们平时的开发中也是比较熟悉的
在objc
源码中可以看到如下的调用顺序
lookupimporforward
-> realizeAndInitializeIfNeeded_locked
-> initializeAndLeaveLocked
-> initializeAndMaybeRelock
-> initializeNonMetaClass
-> callInitialize
-> initialize