iOS开发底层之RuntimeObjc_msgSend探究下 - 09
Posted iOS_developer_zhong
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了iOS开发底层之RuntimeObjc_msgSend探究下 - 09相关的知识,希望对你有一定的参考价值。
文章目录
一、上篇文章补充
1.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
mov p16, \\src // optimistically set dst = src
tbz p16, #ISA_INDEX_IS_NPI_BIT, 1f // done if not non-pointer isa
// isa in p16 is indexed
adrp x10, _objc_indexed_classes@PAGE
add x10, x10, _objc_indexed_classes@PAGEOFF
ubfx p16, p16, #ISA_INDEX_SHIFT, #ISA_INDEX_BITS // extract index
ldr p16, [x10, p16, UXTP #PTRSHIFT] // load class from array
1:
#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
上述代码中GetClassFromIsa_p16是定义的一个宏,主要功能为,通过isa找到对应的类;ExtractISA也是个宏定义,功能为将传入的isa&isaMask,得到class,并将class赋给p16。
- 得到class后,接下来探索下CacheLookup缓存查找流程。
LGetIsaDone:
// calls imp or objc_msgSend_uncached
CacheLookup NORMAL, _objc_msgSend, __objc_msgSend_uncached
二、CacheLookup探索
- 源码探索,看看到底如何命中缓存,
.macro CacheLookup Mode, Function, MissLabelDynamic, MissLabelConstant
mov x15, x16 // stash the original isa
LLookupStart\\Function:
// p1 = SEL, p16 = isa
#if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16_BIG_ADDRS
ldr p10, [x16, #CACHE] // p10 = mask|buckets
lsr p11, p10, #48 // p11 = mask
and p10, p10, #0xffffffffffff // p10 = buckets
and w12, w1, w11 // x12 = _cmd & mask
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16
ldr p11, [x16, #CACHE] // p11 = mask|buckets
#if CONFIG_USE_PREOPT_CACHES
#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 // insert的时候也是右移7位,所以取一样的, 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
#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,对应下标,第一个要查的bucket(sel - imp)
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) 循环条件, 是否超出了buckets内存地址
b.hs 1b
// wrap-around:
// p10 = first bucket
// p11 = mask (and maybe other bits on LP64)
// p12 = _cmd & mask
#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
4: ldp p17, p9, [x13], #-BUCKET_SIZE // imp, sel = *bucket--
cmp p9, p1 // if (sel == _cmd)
b.eq 2b // goto hit
cmp p9, #0 // while (sel != 0 &&
ccmp p13, p12, #0, ne // bucket > first_probed)
b.hi 4b
上述流程,暂且只分析CACHE_MASK_STORAGE_HIGH_16_BIG_ADDRS环境。 重要步骤为 解释 +源码来描述。
接本篇第一大点,获取到class类对象地址后,去进行缓存查找方法。 步骤如下:
- 对类对象地址,进行指针平移16个字节,得到cache_t的首地址,这个可以看前面objc_class的内存结构成员变量表结构, 在cache_t之前,有 isa,还有superclass,所以这里平移16个字节。
#if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16_BIG_ADDRS
ldr p10, [x16, #CACHE] // p10 = mask|buckets
lsr p11, p10, #48 // p11 = mask
- 在真机环境下, mask和buckets共占用64位,mask占高16位, buckets占低48位, 现在为了得到buckets,所以进行了掩码运算, 源码为and p10, p11, #0x0000ffffffffffff, 然后找到buckets后,就涉及到了 cache_hash算法,源码如下:
#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
// cache_hash算法
eor p12, p1, p1, LSR #7 // p1 = _cmd
and p12, p12, p11, LSR #48 // insert的时候也是右移7位,所以取一样的, 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
- cache_hash算法如下:
3.1 p1(_cmd)右移7位,进行异或运算, 然后给 p12 , 然后p11右移48位,得到mask ,然后mask与p12进行与操作获取下标 , 然后赋值给 p12 , 通过上面的操作,我们记录下相关运算后的各参数的结果。
- p11 = _bucketsAndMaybeMask
- p10 = buckets
- p12 = 经过运算后查找的方法的hash下标
- 得到了hash下标, 然后buckets的首地址也知道,现在就开始要寻找bucket
// 循环流程 p13,对应下标,第一个要查的bucket(sel - imp) PTRSHIFT = 3
add p13, p10, p12, LSL #(1+PTRSHIFT)
// p13 = buckets + ((_cmd & mask) << (1+PTRSHIFT))
算法解读为 , PTRSHIFT = 3, 所以 相当于 对 p12进行左移16位, (sel 8位, imp8位), p10是buckets收地址,加上 p12(经过运算,相当于16的倍数位移)。
- 下面进入了一个循环查找过程, 进行sel对比, 直到超出了buckets范围。
// 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) 循环条件, 是否超出了buckets内存地址
b.hs 1b
- 如果上面的循环查找sel,没有找到,就会走到下面的查找流程。 从最后mask的存储位置往前查找。
#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
- 再次,开启了一个循环, 这个循环是从最后一个位置,往前进行查询, 首先重新设置p12的下标。 然后开启查找循环。
add p12, p10, p12, LSL #(1+PTRSHIFT)
// p12 = first probed bucket
// do
4: ldp p17, p9, [x13], #-BUCKET_SIZE // imp, sel = *bucket--
cmp p9, p1 // if (sel == _cmd)
b.eq 2b // goto hit
cmp p9, #0 // while (sel != 0 &&
ccmp p13, p12, #0, ne // bucket > first_probed)
b.hi 4b
// 上述循环,没有走完,就会走到 MissLabelDynamic流程
LLookupEnd\\Function:
LLookupRecover\\Function:
b \\MissLabelDynamic
- 命中缓存的方法 CacheHit探索
// 参数说明:CacheHit: x17 = cached IMP, x10 = address of buckets, x1 = SEL, x16 = isa
.macro CacheHit
.if $0 == NORMAL
TailCallCachedImp x17, x10, x1, x16 // authenticate and call imp
.elseif $0 == GETIMP
mov p0, p17
cbz p0, 9f // don't ptrauth a nil imp
AuthAndResignAsIMP x0, x10, x1, x16 // authenticate imp and re-sign as IMP
9: ret // return IMP
.elseif $0 == LOOKUP
// No nil check for ptrauth: the caller would crash anyway when they
// jump to a nil IMP. We don't care if that jump also fails ptrauth.
AuthAndResignAsIMP x17, x10, x1, x16 // authenticate imp and re-sign as IMP
cmp x16, x15
cinc x16, x16, ne // x16 += 1 when x15 != x16 (for instrumentation ; fallback to the parent class)
ret // return imp via x17
.else
.abort oops
.endif
.endmacro
- 缓存找不到, lookUpImpOrForward - 慢速 - methodlsit
三、lookUpImpOrForward 探索
1 lookUpImpOrForward 慢速查找实现
- 一起来看下这个方法里面是如何实现的, 上源码:
IMP lookUpImpOrForward(id inst, SEL sel, Class cls, int behavior)
const IMP forward_imp = (IMP)_objc_msgForward_impcache;
IMP imp = nil;
Class curClass;
runtimeLock.assertUnlocked();
runtimeLock.lock(); //1.多线程保护,进行安全加锁
checkIsKnownClass(cls); //2.判断当前类有没有进行注册到缓存表, 注册类
cls = realizeAndInitializeIfNeeded_locked(inst, cls, behavior & LOOKUP_INITIALIZE); //3. ro ,rw的处理,按照isa流程递归初始化,并赋值。 为了方便后续的查方法。自身方法找不到,就找父类, 类方法就找元类。
// ro ,rw 存放了属性与方法列表。
// runtimeLock may have been dropped but is now locked again
runtimeLock.assertLocked();
curClass = cls;
for (unsigned attempts = unreasonableClassCount();;)
if (curClass->cache.isConstantOptimizedCache(/* strict */true)) // 4. 共享缓存再查找一遍这个SEL, 是为了防止上面进行了一系列操作,刚好插入了这个方法。
#if CONFIG_USE_PREOPT_CACHES
imp = cache_getImp(curClass, sel);
if (imp) goto done_unlock;
curClass = curClass->cache.preoptFallbackClass();
#endif
else
// curClass method list.
// 5. 重要: 使用了二分查找法,去寻找对应的sel , 最终的算法实现探索路径为 getMethodNoSuper_nolock -》 search_method_list_inline -》 findMethodInSortedMethodList
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.
// 6. 去父类中查找方法
// 快速 -》 慢速 -》父类的父类 (递归操作 找到nil为止) -》 最终返回 forward_imp
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;
// No implementation found. Try method resolver once.
if (slowpath(behavior & LOOKUP_RESOLVER))
behavior ^= LOOKUP_RESOLVER;
return resolveMethod_locked(inst, sel, cls, behavior);
done:
// 找到了方法后,执行的操作
if (fastpath((behavior & LOOKUP_NOCACHE) == 0))
#if CONFIG_USE_PREOPT_CACHES
while (cls->cache.isConstantOptimizedCache(/* strict */true))
cls = cls->cache.preoptFallbackClass();
#endif
// 缓存没有这个方法,就把这个方法插入到缓存表
log_and_fill_cache(cls, imp, sel, inst, curClass);
done_unlock:
runtimeLock.unlock();
if (slowpath((behavior & LOOKUP_NIL) && imp == forward_imp))
return nil;
return imp;
static method_t *
getMethodNoSuper_nolock(Class cls, SEL sel)
runtimeLock.assertLocked();
ASSERT(cls->isRealized());
// fixme nil cls?
// fixme nil sel?
auto const methods = cls->data()->methods();
for (auto mlists = methods.beginLists(),
end = methods.endLists();
mlists != end;
++mlists)
method_t *m = search_method_list_inline(*mlists, sel);
if (m) return m;
return nil;
说明: 慢速查找方法流程
1. 查自己的 methodList - sel -> imp
2. 查父类 -> NSObject -> nil -> 跳出来。 (对象方法)
3. 查元类 (类方法才会走这个流程查找方法)
template<class getNameFunc>
ALWAYS_INLINE static method_t *
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;
// >> 1 举例: 1000 >> 1 = 0100 = 4 ,减半
// 设计很巧妙,后面再详细写下步骤
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;
2 总结下lookUpImpOrForward方法主要做了什么:
- 判断类是否初始化
- 判断类是否注册到缓存表
- 对类进行赋值 RO RW ,递归实现父类和元类
- 从缓存中查找imp ,有的话就直接返回
- 采用二分查找法从方法列表中查找imp ,找到了,就插入到缓存表,下次访问就可以快速访问了。
- 判断当前类的父类是否存在, 不存在就进入消息转发
- 父类存在, 就开始快速查找 -》 慢速查找 -》 递归父类 ,直到nil ,找到imp就返回, 反之,就返回forward_imp
以上是关于iOS开发底层之RuntimeObjc_msgSend探究下 - 09的主要内容,如果未能解决你的问题,请参考以下文章