前言:
前文已经分析了方法的快速查流程,但是当方法没有缓存的时候,会执行哪些函数呢,带着这个问题,开始今天的探索。
通过前文的分析,已经知道,在方法缓存查找失败后,会执行__objc_msgSend_uncached
,先回顾一下__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 ,参数为imp
TailCallFunctionPointer x17
END_ENTRY __objc_msgSend_uncached
MethodTableLookup
解析
.macro MethodTableLookup
SAVE_REGS MSGSEND
// lookUpImpOrForward(obj, sel, cls, LOOKUP_INITIALIZE | LOOKUP_RESOLVER)
// receiver and selector already in x0 and x1
mov x2, x16
mov x3, #3
bl _lookUpImpOrForward
// 将lookUpImpOrForward的返回值imp 给x17
// IMP in x0
mov x17, x0
RESTORE_REGS MSGSEND
.endmacro
TailCallFunctionPointer解析
//执行imp
.macro TailCallFunctionPointer
// $0 = function pointer value
br $0
.endmacro
通过源码我们可以知道,在MethodTableLookup
会调用lookUpImpOrForward
函数并返回imp,全局搜索lookUpImpOrForward
,在objc-runtime-new.mm
文件找到了实现代码如下:
1.lookUpImpOrForward
解析:
IMP lookUpImpOrForward(id inst, SEL sel, Class cls, int behavior)
{
// 指定消息转发的imp
const IMP forward_imp = (IMP)_objc_msgForward_impcache;
IMP imp = nil;
Class curClass;
runtimeLock.assertUnlocked();
//判断cls是否加载,如果为真,behavior |= LOOKUP_NOCACHE;behavior = 11
if (slowpath(!cls->isInitialized())) {
// The first message sent to a class is often +new or +alloc, or +self
// which goes through objc_opt_* or various optimized entry points.
//
// However, the class isn't realized/initialized yet at this point,
// and the optimized entry points fall down through objc_msgSend,
// which ends up here.
//
// We really want to avoid caching these, as it can cause IMP caches
// to be made with a single entry forever.
//
// Note that this check is racy as several threads might try to
// message a given class for the first time at the same time,
// in which case we might cache anyway.
behavior |= LOOKUP_NOCACHE;
}
// runtimeLock is held during isRealized and isInitialized checking
// to prevent races against concurrent realization.
// runtimeLock is held during method search to make
// method-lookup + cache-fill atomic with respect to method addition.
// Otherwise, a category could be added but ignored indefinitely because
// the cache was re-filled with the old value after the cache flush on
// behalf of the category.
runtimeLock.lock();
// We don't want people to be able to craft a binary blob that looks like
// a class but really isn't one and do a CFI attack.
//
// To make these harder we want to make sure this is a class that was
// either built into the binary or legitimately registered through
// objc_duplicateClass, objc_initializeClassPair or objc_allocateClassPair.
//检查类是否已经注册
checkIsKnownClass(cls);
//懒加载类,加载类,并递归加载父类,元类,调用initialize方法
cls = realizeAndInitializeIfNeeded_locked(inst, cls, behavior & LOOKUP_INITIALIZE);
// runtimeLock may have been dropped but is now locked again
runtimeLock.assertLocked();
curClass = cls;
// The code used to lookup the class's cache again right after
// we take the lock but for the vast majority of the cases
// evidence shows this is a miss most of the time, hence a time loss.
//
// The only codepath calling into this without having performed some
// kind of cache lookup is class_getInstanceMethod().
//unreasonableClassCount 根据函数的注释翻译为: 为类的任何迭代提供上限,在运行时元数据损坏时防止旋转。
// 死循环,根据,goto,break退出循环
for (unsigned attempts = unreasonableClassCount();;) {
//判断是否有共享缓存优化
if (curClass->cache.isConstantOptimizedCache(/* strict */true)) {
#if CONFIG_USE_PREOPT_CACHES //判断是否支持共享缓存,真机支持
//查询共享缓存,如果在查找这个方法时,其它线程已经调用并存储了这个方法,共享缓存中就存在了
//通过sel在curClass的缓存中查找imp
imp = cache_getImp(curClass, sel);
//如果查询到imp,跳转到 done_unlock流程
if (imp) goto done_unlock;
curClass = curClass->cache.preoptFallbackClass();
#endif
} else {
//在curClass方法列表中,根据sel查找Method
// curClass method list.
Method meth = getMethodNoSuper_nolock(curClass, sel);
//如果meth存在取出imp,并跳转到 done
if (meth) {
imp = meth->imp(false);
goto done;
}
//在本类的方法列表未查找到,将curClass赋值为父类并判断是否是否存在
if (slowpath((curClass = curClass->getSuperclass()) == nil)) {
// No implementation found, and method resolver didn't help.
// Use forwarding.
// 按照继承链(cls->supercls->nil)一直查找到nil都没有查找到sel对应的IMP
// 且动态方法决议也没起作用,就开始消息转发
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.
//根据sel在父类的缓存中查找imp
imp = cache_getImp(curClass, sel);
//判断从父类查找到的imp是否为forward_imp,如果是则跳出循环
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;
}
//如果imp存在并不为forward_imp,跳转到done
if (fastpath(imp)) {
// Found the method in a superclass. Cache it in this class.
goto done;
}
}
// No implementation found. Try method resolver once.
// 执行方法动态决议,behavior传值为3,如果!cls->isInitialized()未YES,behavior = 11,LOOKUP_RESOLVER = 2
// 3 & 2 = 1,8 & 2 = 1,第一次进入时为YES
if (slowpath(behavior & LOOKUP_RESOLVER)) {
//3 ^ 2 = 1,11 ^ 2 = 9, 1,9 再次 & 2 都为0,所以此方法只会执行一次
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
// 查找到imp,将sel,imp存储到cls的cache,即调用cache_t::insert函数,之前在类的结构中已分析过此函数
log_and_fill_cache(cls, imp, sel, inst, curClass);
}
done_unlock:
runtimeLock.unlock();
//imp为找到,并且imp == forward_imp 返回nil
if (slowpath((behavior & LOOKUP_NIL) && imp == forward_imp)) {
return nil;
}
return imp;
}
慢速查找流程解析:
- 指定消息转发的imp
forward_imp
为_objc_msgForward_impcache
。 !cls->isInitialized()
判断类是否已加载。checkIsKnownClass(cls);
检查类是否已经注册。realizeAndInitializeIfNeeded_locked
懒加载类,加载类,并递归加载父类,元类,调用initialize方法。 循环查找curClass->cache.isConstantOptimizedCache
判断是否有共享缓存优化imp = cache_getImp(curClass, sel)
通过sel在curClass的缓存中查找imp,如果查询到imp,跳转到 done_unlock流程。Method meth = getMethodNoSuper_nolock(curClass, sel);
使用二分查找法在curClass方法列表中,根据sel查找Method,如果meth存在取出imp,并跳转到 done。curClass = curClass->getSuperclass()) == nil
在本类的方法列表未查找到,将curClass赋值未父类并判断是否是否存在,如果判断父类为nil
依然未找到imp
,并且动态方法决议依然未找到imp
,开始方法转发。imp = cache_getImp(curClass, sel);
根据sel在父类的缓存中查找imp,如果查找到的imp
为forward_imp
则跳出循环,如果不是,跳转到done
if (slowpath(behavior & LOOKUP_RESOLVER))
判断是否执行动态方法决议,此函数只会执行一次log_and_fill_cache
查找到imp,将sel,imp存储到cls的cache,即调用cache_t::insert函数,OC底层原理之类的结构分析已经分析过此函数。
1.1realizeAndInitializeIfNeeded_locked
函数解析:
static Class
realizeAndInitializeIfNeeded_locked(id inst, Class cls, bool initialize)
{
runtimeLock.assertLocked();
//首次给懒加载类发消息时调用
if (slowpath(!cls->isRealized())) {
cls = realizeClassMaybeSwiftAndLeaveLocked(cls, runtimeLock);
// runtimeLock may have been dropped but is now locked again
}
if (slowpath(initialize && !cls->isInitialized())) {
//判断是否需要调用initialize方法
cls = initializeAndLeaveLocked(cls, inst, runtimeLock);
// runtimeLock may have been dropped but is now locked again
// If sel == initialize, class_initialize will send +initialize and
// then the messenger will send +initialize again after this
// procedure finishes. Of course, if this is not being called
// from the messenger then it won't happen. 2778172
}
return cls;
}
realizeClassMaybeSwiftAndLeaveLocked
加载类并递归加载元类,父类initializeAndLeaveLocked
调用initialize
1.2cache_getImp
函数解析:
//参数:p0 = curClass
STATIC_ENTRY _cache_getImp
// 参数 src = curClass needs_auth = 0,此时将curClass给到p16
GetClassFromIsa_p16 p0, 0
// 重新进入CacheLookup查找imp
CacheLookup GETIMP, _cache_getImp, LGetImpMissDynamic, LGetImpMissConstant
// 查询imp失败执行
// 置空p0并返回
LGetImpMissDynamic:
mov p0, #0
ret
LGetImpMissConstant:
mov p0, p2
ret
END_ENTRY _cache_getImp
GetClassFromIsa_p16
得到p16
=cls
CacheLookup
进入查询流程,查询具体流程见OC底层原理objc_msgSend之方法的快速查找流程
CacheHit:解析:
// CacheHit: x17 = cached IMP, x10 = address of buckets, x1 = SEL, x16 = isa
// 此时$0 = GETIMP
.macro CacheHit
.if $0 == NORMAL
TailCallCachedImp x17, x10, x1, x16 // authenticate and call imp
.elseif $0 == GETIMP
// 将p17(imp)赋值给p0,p0寄存器返回值
mov p0, p17
// p0和0进行比较,为0则跳转 9 直接返回p0
cbz p0, 9f // don't ptrauth a nil imp
// 将查询到的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
- 此时
$0 = GETIMP
,执行.elseif $0 == GETIMP
分支,mov p0, p17
将p17(imp)赋值给p0,p0寄存器返回值 cbz p0, 9f
判断p0
是否为空,为空则直接返回AuthAndResignAsIMP
将查询到的imp进行解码
AuthAndResignAsIMP
解析:
.macro AuthAndResignAsIMP
// $0 = cached imp, $1 = address of cached imp, $2 = SEL , $3 = cls
// imp = imp ^ cls 和bucket_t 的 encodeImp函数(uintptr_t)newImp ^ (uintptr_t)cls;对应
eor $0, $0, $3
.endmacro
imp = imp ^ cls
和bucket_t
的encodeImp
函数(uintptr_t)newImp ^ (uintptr_t)cls;
对应
getMethodNoSuper_nolock
解析:
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();
// beginLists为最后添加到方法列表的指针
for (auto mlists = methods.beginLists(),
end = methods.endLists();
mlists != end;
++mlists)
{
// <rdar://problem/46904873> 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.
//根据sel查询方法
method_t *m = search_method_list_inline(*mlists, sel);
if (m) return m;
}
return nil;
}
- 遍历二维指针
methods
,后添加的先查询
search_method_list_inline
函数解析:
ALWAYS_INLINE static method_t *
search_method_list_inline(const method_list_t *mlist, SEL sel)
{
int methodListIsFixedUp = mlist->isFixedUp();
int methodListHasExpectedSize = mlist->isExpectedSize();
// method列表是否修复过(进行排序过,这部分是在类的加载的时候做的事情)
if (fastpath(methodListIsFixedUp && methodListHasExpectedSize)) {
return findMethodInSortedMethodList(sel, mlist);
} else {// 未修复的不做分析
// Linear search of unsorted method list
if (auto *m = findMethodInUnsortedMethodList(sel, mlist))
return m;
}
// DEBUG 不做分析
#if DEBUG
// sanity-check negative results
if (mlist->isFixedUp()) {
for (auto& meth : *mlist) {
if (meth.name() == sel) {
_objc_fatal("linear search worked when binary search did not");
}
}
}
#endif
return nil;
}
findMethodInSortedMethodList(SEL key, const method_list_t *list)
函数解析:
ALWAYS_INLINE static method_t *
findMethodInSortedMethodList(SEL key, const method_list_t *list)
{
if (list->isSmallList()) {// 根据架构不同判断是否small
if (CONFIG_SHARED_CACHE_RELATIVE_DIRECT_SELECTORS && objc::inSharedCache((uintptr_t)list)) {
return findMethodInSortedMethodList(key, list, [](method_t &m) { return m.getSmallNameAsSEL(); });
} else {
return findMethodInSortedMethodList(key, list, [](method_t &m) { return m.getSmallNameAsSELRef(); });
}
} else {
return findMethodInSortedMethodList(key, list, [](method_t &m) { return m.big().name; });
}
}
findMethodInSortedMethodList(SEL key, const method_list_t *list, const getNameFunc &getName)
函数解析:
//二分查找法
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;
// probe 推断为 first的类型
decltype(first) probe;
// 将sel转换成uintptr_t,此时已排序
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))) {
//如果前一个的名字sel也一样,并且不是起始位置,由于方法以及按照名称排序,前一个名称一样, 则表示前一个方法在主类和分类或多个分类中都有实现,说明了如果分类重写了主类的方法,调用该方法的时候会调用分类的方法:多个分类有相同的方法,则按照加载顺序执行最后加载的方法
probe--;
}
return &*probe;
}
if (keyValue > probeValue) {
base = probe + 1;
count--;
}
}
return nil;
}
二分查找法举例说明:假设需要查询的方法在第5位,方法 count
=8
,第一次查询 probe.index = 4
,不满足,进入if (keyValue > probeValue)
判断,base
为第5
位,count-- 为7
,再次进入循环,count >> 1 = 3
,probe = base + (count >> 1)
probe
的index = 6
,不满足,再次进入循环,count >> 1
= 0
probe
= base
,index
= 5
, 满足条件
图解:
二分查找法测试:
对findMethodInSortedMethodList
函数做如下修改:
如下定义一个 XQPerson
类
@interface XQPerson : NSObject
-(void)playGame;
-(void)eatFood;
-(void)drink;
-(void)watchMovie;
-(void)walk;
-(void)run;
-(void)study;
-(void)playBaskeyBall;
@end
@implementation XQPerson
-(void)playGame{
NSLog(@"%s",__func__);
}
-(void)eatFood{
NSLog(@"%s",__func__);
}
-(void)drink{
NSLog(@"%s",__func__);
}
-(void)watchMovie{
NSLog(@"%s",__func__);
}
-(void)walk{
NSLog(@"%s",__func__);
}
-(void)run{
NSLog(@"%s",__func__);
}
-(void)study{
NSLog(@"%s",__func__);
}
-(void)playBaskeyBall{
NSLog(@"%s",__func__);
}
@end
一次调用其中的方法如下:
int main(int argc, const char * argv[]) {
@autoreleasepool {
XQPerson* person = [XQPerson alloc];
[person eatFood];
printf("\n");
[person walk];
printf("\n");
[person run];
printf("\n");
[person watchMovie];
printf("\n");
[person playGame];
printf("\n");
[person playBaskeyBall];
printf("\n");
[person study];
printf("\n");
[person drink];
printf("\n");
}
return 0;
}
********************************调试结果****************************************
name:walk------------------probeValue:4294982885 ----------------index:4
name:drink------------------probeValue:4294982868 ----------------index:2
name:eatFood------------------probeValue:4294982860 ----------------index:1
2022-02-16 16:34:27.805291+0800 KCObjcBuild[37811:636981] -[XQPerson eatFood]
2022-02-16 16:34:27.806164+0800 KCObjcBuild[37811:636981] -[XQPerson walk]
name:walk------------------probeValue:4294982885 ----------------index:4
name:playBaskeyBall------------------probeValue:4294982900 ----------------index:6
name:run------------------probeValue:140735269552228 ----------------index:7
2022-02-16 16:34:27.806353+0800 KCObjcBuild[37811:636981] -[XQPerson run]
name:walk------------------probeValue:4294982885 ----------------index:4
name:drink------------------probeValue:4294982868 ----------------index:2
name:watchMovie------------------probeValue:4294982874 ----------------index:3
2022-02-16 16:34:27.806486+0800 KCObjcBuild[37811:636981] -[XQPerson watchMovie]
name:walk------------------probeValue:4294982885 ----------------index:4
name:drink------------------probeValue:4294982868 ----------------index:2
name:eatFood------------------probeValue:4294982860 ----------------index:1
name:playGame------------------probeValue:4294982851 ----------------index:0
2022-02-16 16:34:27.812755+0800 KCObjcBuild[37811:636981] -[XQPerson playGame]
name:walk------------------probeValue:4294982885 ----------------index:4
name:playBaskeyBall------------------probeValue:4294982900 ----------------index:6
2022-02-16 16:34:27.813065+0800 KCObjcBuild[37811:636981] -[XQPerson playBaskeyBall]
name:walk------------------probeValue:4294982885 ----------------index:4
name:playBaskeyBall------------------probeValue:4294982900 ----------------index:6
name:study------------------probeValue:4294982894 ----------------index:5
2022-02-16 16:34:27.813203+0800 KCObjcBuild[37811:636981] -[XQPerson study]
name:walk------------------probeValue:4294982885 ----------------index:4
name:drink------------------probeValue:4294982868 ----------------index:2
2022-02-16 16:34:27.813313+0800 KCObjcBuild[37811:636981] -[XQPerson drink]
log_and_fill_cache
函数解析:
static void
log_and_fill_cache(Class cls, IMP imp, SEL sel, id receiver, Class implementer)
{
#if SUPPORT_MESSAGE_LOGGING
if (slowpath(objcMsgLogEnabled && implementer)) {
bool cacheIt = logMessageSend(implementer->isMetaClass(),
cls->nameForLogging(),
implementer->nameForLogging(),
sel);
if (!cacheIt) return;
}
#endif
// 插入缓存
cls->cache.insert(sel, imp, receiver);
}
cls->cache.insert
,将查找到的imp
和sel
插入cls
的cache
1.3方法慢速查找流程图:
总结:objc_msgSend在快速查找失败后会进入方法慢速查找流程,即lookUpImpOrForward
,大致流程为加载类信息(方法列表,属性列表,协议列表等),确认当前类的继承链,使用二分查找法在本类的方法列表中查找方法,如找到则插入缓存并返回,未找到则依次遍历父类,先在父类的缓存列表中查找如找到则插入本类缓存,如未找到则使用二分查找法查找父类的方法列表如找到 imp
后将 imp
添加到缓存后返回,未找到则进入动态方法决议流程。