iOS 底层第9
天的学习。今天主要分享内容还是objc_msgSend
,在第8
天的学习中,分析了object_msgSend
的流程,但有一个地方并没有深入下去,就是当没有找到 sel == 0
时,会进入 __objc_msgSend_uncached
,那么这个__objc_msgSend_uncached
到底是如何实现?
objc_msgSend_uncached
- 搜索
objc_msgSend_uncached
STATIC_ENTRY __objc_msgSend_uncached // 进入 __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
STATIC_ENTRY __objc_msgLookup_uncached
UNWIND __objc_msgLookup_uncached, FrameWithNoSaves
// THIS IS NOT A CALLABLE C FUNCTION
// Out-of-band p15 is the class to search
MethodTableLookup
ret
- 分析👆代码,可知
imp
.存在MethodTableLookup
这个里面 ,搜索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
// IMP in x0
mov x17, x0
RESTORE_REGS MSGSEND
.endmacro
mov x17, x0
,汇编——如果在x0
里面 必然会返回,x0
是第一位的计数器,也是返回值的存储位置,所以x0
在bl _lookUpImpOrForward
的这个返回值里面 =>x0 = imp
-
搜索不到
_lookUpImpOrForward
汇编代码。把_
去掉,继续搜索找到 代码,接下来就从汇编
流程进入到了c/c++
流程。
当我们缓存里找不到的时候会进入
lookUpImpOrForward
那为什么缓存要用汇编,而不用
c/c++
呢?
- 汇编更接近于机器语言执行非常的快(让对象能在底层更加快速的在缓存中找到方法)
- 汇编能更加快速的查找优化时间
- 汇编能更加的动态化。有些方法参数是未知,而
c
语言无法满足。
那为什么
lookUpImpOrForward
又要用c/c++
?
- 因为接下来是一个慢速查找的流程, 缓存里已经找不到了, 就要到
methodlist
去查找方法。
lookUpImpOrForward
- 我们接下来开始分析
lookUpImpOrForward
,它到底是怎么找到imp
。 - 我们直接分析主要的代码 找到
curClass method list.
// 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;
}
- 进入
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();
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.
method_t *m = search_method_list_inline(*mlists, sel);
if (m) return m;
}
return nil;
}
-
getMethodNoSuper_nolock
里是一个二维数组的循环去获取method_t
- 再进入
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();
if (fastpath(methodListIsFixedUp && methodListHasExpectedSize)) {
return findMethodInSortedMethodList(sel, mlist);
} else {
// Linear search of unsorted method list
if (auto *m = findMethodInUnsortedMethodList(sel, mlist))
return m;
}
}
- 再进入
findMethodInSortedMethodList
ALWAYS_INLINE static method_t *
findMethodInSortedMethodList(SEL key, const method_list_t *list)
{
if (list->isSmallList()) {
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; });
}
}
- 如果是正常的电脑会走
else
,return findMethodInSortedMethodList(key, list, [](method_t &m) { return m.big().name; });
- 继续
findMethodInSortedMethodList
终于看到了如下代码
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;
// >>>>>>>>>>>>>>> 我们开始进行分析,这个循环到底是如何找到 probe
// 我们假设 count = 7 => 0111
for (count = list->count; count != 0; count >>= 1) {
// count = 7 进来 , count !=0 继续往下走
probe = base + (count >> 1);
// base (一号位置) = 0, (count >> 1) => 0111 >> 1 = 0011 = 3
// probe = 0 + 3 => probe = 3
// ----------------------------------------- count-- 后在进遍历
// 这时 base = 4,count = 6,count !=0 继续往下走
// count >>= 1 => 0110 >> 1 => 0010 = 2
// 为什么 count = 2 呢?,
// 我们前面已经得到了 probe = 3 ,所以 count 就完美的避开了 3 来到了 2
// base = 4,count = 6 , 取值应该在4~6 => 5
// probe = base + (count >> 1); => probe = 4 + ( 0010 >> 1 = 1) => probe = 5
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.
// 寻找分类 category , 当分类和主类有相同的方法调用 分类的方法
while (probe > first && keyValue == (uintptr_t)getName((probe - 1))) {
probe--;
}
return &*probe;// 返回
}
if (keyValue > probeValue) {
// 如果没能匹配上
base = probe + 1;
// 之前的 probe = 3 => base = 3 + 1 => base = 4
count--;
// count-- 之前的 count = 7 => count = 7
}
}
return nil;
}
- 根据上面的代码的注释我们可以知道,这个算法已经把二分的查找流程写的太极致了,每一次
>>1
都是有他的意义所在。 - 当我们根据二分查找找到了
method
,根据method->imp
,继续往下goto done
if (meth) {
imp = meth->imp(false);
goto done;
}
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);
}
- 如果能匹配到
imp
-
done
之后就会进入log_and_fill_cache
=>cls->cache.insert(sel, imp, receiver);
插入到缓存中 - 这时我们可以得出一个结论——在第一次
lookup imp
的时候,会objc_msgSend
从缓存中查找,如果缓存里没有就会在ro
,rw
里进入慢速查找,如果慢速没找到就会error
,如果找到了就会在缓存里insert
。当前我们下次在去调用时就会objc_msgSend
从缓存中快速lookup imp
如果不能匹配到
imp
,继续👇代码
// 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_getImp
,这里的cache_getImp
其实就是汇编流程跟objc_msgSend
是一样的。
STATIC_ENTRY _cache_getImp
GetClassFromIsa_p16 p0, 0
CacheLookup GETIMP, _cache_getImp, LGetImpMissDynamic, LGetImpMissConstant
- 分析
cache_getImp
中的curClass
何时被赋值
if (slowpath((curClass = curClass->getSuperclass()) == nil)) {
// No implementation found, and method resolver didn't help.
// Use forwarding.
imp = forward_imp;
break;
}
- 当在父类进行慢速查找时,没找到的话会进入父类的父类,其实就是一个递归。但递归到最后还没找到返回
nil
时就会进入forward_imp
流程&总结
查找imp流程.jpg