前言
在上一篇底层原理之Runimte 运行时我们探索了objc_msgSend的方法查找流程。objc_msgSend底层是用汇编写的快速高效查找缓存的方法,我们称之为快速查找,通过SEL查找IMP,如果命中则返回IMP,如果没有命中就会进入objc_msgSend_uncached,下面就探索一下在快速查找找不到的情况下会怎么处理。附oc源码。
1.0 objc_msgSend_uncached
接着上一篇底层原理之Runimte 运行时如果在cache_t中循环查找找不到IMP时会进入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
分析:objc_msgSend_uncached汇编就两行,MethodTableLookup和TailCallFunctionPointer x17,我们具体看下。
1.1 MethodTableLookup
.macro MethodTableLookup
SAVE_REGS MSGSEND
//x0和x1 是receiver和sel,上一篇分析得出x16=p16=class
//x2=x16=class
mov x2, x16
mov x3, #3 //x3=3
// lookUpImpOrForward(obj, sel, cls, LOOKUP_INITIALIZE | LOOKUP_RESOLVER)
bl _lookUpImpOrForward //b跳转指令,lr寄存处,bl意思:执行_lookUpImpOrForward,并把一下一行指令mov x17, x0放到lr寄存器中
// IMP in x0
mov x17, x0 //x0为执行_lookUpImpOrForward返回值IMP
RESTORE_REGS MSGSEND
.endmacro
分析:方法查找的默认两个参数x0=reservier,x1=sel,上述汇编把x2=class,x3=3,然后执行方法_lookUpImpOrForward,x0、x1、x2、x3是_lookUpImpOrForward方法的形参。lr寄存器存入下一行指令mov x17, x0,那么lookUpImpOrForward方法执行完会执行mov x17, x0,此时的x0是lookUpImpOrForward返回值,我们研究此函数的意义就是查找IMP,所以猜测返回值应该是IMP,即x17=x0=IMP
1.2 TailCallFunctionPointer
.macro TailCallFunctionPointer
// $0 =x17=IMP
br $0
.endmacro
分析:MethodTableLookup执行完成后x17=IMP,TailCallFunctionPointer就是执行IMP方法实现。
2.0 lookUpImpOrForward
lookUpImpOrForward是objc_msgSend_uncached的关键,因为它返回的是IMP,全局搜索lookUpImpOrForward,发现lookUpImpOrForward已不再是汇编代码。我们先总体看一下具体实现,然后再详细分析。
//根据上面分析传入的参数x0=reservier、x1=sel、x2=class、x3=3
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();
if (slowpath(!cls->isInitialized())) {
// 发送到类的第一条消息通常是 +new 或 +alloc 或 +self
// 它通过 objc_opt_* 或各种优化的入口点。
// 但是此时该类尚未通过realized/initialized 实现或初始化,
// 我们真的想避免缓存这些,因为它会导致 IMP 缓存永远只用一个条目。
//请注意,此检查很活跃,因为多个线程可能会同时第一次尝试向给定类发送消息,在这种情况下,我们可能会进行缓存
//behavior=behavior|LOOKUP_NOCACHE= 3|8= 0011|1000=1011=11
behavior |= LOOKUP_NOCACHE;
}
// runtimeLock 在 isRealized 和 isInitialized 检查期间保持,以防止与并发实现竞争。
//在方法搜索期间持有 runtimeLock 以使方法查找 + 缓存填充相对于方法添加具 有原子性。
//否则,可能会添加一个类别,但会被无限期地忽略,因为在代表该类别的缓存刷新后,缓存被重新填充了旧值。
runtimeLock.lock();
// 我们要确保这是一个内置在二进制文件中或通过 objc_duplicateClass、 objc_initializeClassPair 或 objc_allocateClassPair 合法注册的类。
//即被dyld加载的类
checkIsKnownClass(cls);
//初始化类、父类、元类,加载方法等 3&1=0011&0001=0001=true
cls = realizeAndInitializeIfNeeded_locked(inst, cls, behavior & LOOKUP_INITIALIZE);
runtimeLock.assertLocked();
curClass = cls;
// 用于在我们获取锁后立即再次查找类的缓存的代码,但对于绝大多数情况,证据表明这在大多数情况下是未命中的,因此会浪费时间。
//没有执行某种缓存查找的情况下调用此方法的唯一代码路径是 class_getInstanceMethod()。
for (unsigned attempts = unreasonableClassCount();;) {
//判断是否有共享缓存缓存优化,一般是系统的方法比如NSLog,一般的方法不会走
if (curClass->cache.isConstantOptimizedCache(/* strict */true)) {
/*
查询共享缓存中有没有该方法,缓存中根据sel查询imp
*/
#if CONFIG_USE_PREOPT_CACHES //真机环境下会走
imp = cache_getImp(curClass, sel);
//如果缓存中有就进入done_unlock
if (imp) goto done_unlock;
curClass = curClass->cache.preoptFallbackClass();
#endif
} else {
// curClass中采用二分法查找对应的Method
Method meth = getMethodNoSuper_nolock(curClass, sel);
if (meth) {//找到method 执行done
imp = meth->imp(false);
goto done;
}
//curClass赋值为父类,如果为父类为空 imp=forward_imp 跳出循环
if (slowpath((curClass = curClass->getSuperclass()) == nil)) {
imp = forward_imp;
break;
}
}
// 如果父类中有循环,则停止。
if (slowpath(--attempts == 0)) {
_objc_fatal("Memory corruption in class list.");
}
// 此时curClass = curClass->getSuperclass(),在父类缓存中查找
imp = cache_getImp(curClass, sel);
if (slowpath(imp == forward_imp)) {
//如果父类中找到的是forward_imp 就停止查找,跳出循环
break;
}
//父类中找到Imp 进入done
if (fastpath(imp)) {
goto done;
}
}
// 如果查询方法的没有实现,系统会尝试一次动态方法解析
//第一次 behavior & LOOKUP_RESOLVER =3 & 2=0011 & 0010=0010=2
//第二次进入下面behavior=1,behavior & LOOKUP_RESOLVER=1&2=0001&0010=0 不会进入动态方法解析
if (slowpath(behavior & LOOKUP_RESOLVER)) {
//behavior= behavior ^= LOOKUP_RESOLVER=3^2=0011^0010=0001=1
behavior ^= LOOKUP_RESOLVER;
//动态方法解析
return resolveMethod_locked(inst, sel, cls, behavior);
}
done:
//behavior & LOOKUP_NOCACHE=3&8=0011&1000=0执行
//alloc或init时behavior=11,即 1011 & 1000=1000!=0,所以不会插入缓存
if (fastpath((behavior & LOOKUP_NOCACHE) == 0)) {
#if CONFIG_USE_PREOPT_CACHES //真机下
while (cls->cache.isConstantOptimizedCache(/* strict */true)) {
cls = cls->cache.preoptFallbackClass();
}
#endif
//把sel和Imp插入cls中的cache_t
log_and_fill_cache(cls, imp, sel, inst, curClass);
}
done_unlock:
runtimeLock.unlock();
//如果没有查询到返回nil
if (slowpath((behavior & LOOKUP_NIL) && imp == forward_imp)) {
return nil;
}
return imp;
}
分析:整体分析一下lookUpImpOrForward慢速查找流程,慢速是区别于汇编快速查找,此过程是不断的在本类和父类的缓存中查找方法。方法的作用已经注释,注释很重要。
初始化类、父类、元类,初始化方法、属性、协议,一般是alloc或init才会调用- 判断是否有
共享缓存,目的是有可能在查过过程中这个方法被调用缓存了,如果有的话直接从缓存中取,没有共享缓存则开始到本类中查询 - 在本类中
二分查找sel对应的IMP,如果找到IMP那么插入本类的cache_t中 - 本类没有找到IMP就在
父类的缓存中循环查找,找到就插入到本类的cache_t中,否则跳出循环执行消息转发。 - 如果本类和父类都没有查询到,此时系统会给你一次机会,判断是否执行过
动态方法决议,如果没有则走动态方法决议。 注意:alloc和init不会插入cache_t中
根据上面的分析详细分析一下下面几个函数
2.1 realizeAndInitializeIfNeeded_locked
static Class
realizeAndInitializeIfNeeded_locked(id inst, Class cls, bool initialize)
{
runtimeLock.assertLocked();
//slowpath和fastpath的意思是发生的概率小和发生的概率大,去掉这个也没影响,只是告诉编译器,让编译器优化
//判断类是否实现
if (slowpath(!cls->isRealized())) {
//跟进realizeClassMaybeSwiftAndLeaveLocked实现,其实是实现了 realizeClassWithoutSwift 初始化了类、父类、元类
cls = realizeClassMaybeSwiftAndLeaveLocked(cls, runtimeLock);
}
//把类关联进表
if (slowpath(initialize && !cls->isInitialized())) {
cls = initializeAndLeaveLocked(cls, inst, runtimeLock);
// 如果 sel == initialize,class_initialize 将发送 +initialize,然后在此过程完成后,messenger 将再次发送 +initialize
}
return cls;
}
分析:
- realizeClassMaybeSwiftAndLeaveLocked中
realizeClassWithoutSwift初始化了类、父类、元类的ro、rw,以及初始化了方法、属性、协议。 - initializeAndLeaveLocked中
initializeNonMetaClass有个函数remapClass主要作用是把类关联符号表。
2.2 realizeClassWithoutSwift
static Class realizeClassWithoutSwift(Class cls, Class previously)
{
runtimeLock.assertLocked();
class_rw_t *rw;
Class supercls;
Class metacls;
if (!cls) return nil;
if (cls->isRealized()) {
validateAlreadyRealizedClass(cls);
return cls;
}
ASSERT(cls == remapClass(cls));
auto ro = (const class_ro_t *)cls->data();
auto isMeta = ro->flags & RO_META;
//初始化ro rw
if (ro->flags & RO_FUTURE) {
rw = cls->data();
ro = cls->data()->ro();
ASSERT(!isMeta);
cls->changeInfo(RW_REALIZED|RW_REALIZING, RW_FUTURE);
} else {
rw = objc::zalloc<class_rw_t>();
rw->set_ro(ro);
rw->flags = RW_REALIZED|RW_REALIZING|isMeta;
cls->setData(rw);
}
cls->cache.initializeToEmptyOrPreoptimizedInDisguise();
#if FAST_CACHE_META
if (isMeta) cls->cache.setBit(FAST_CACHE_META);
#endif
cls->chooseClassArrayIndex();
if (PrintConnecting) {
_objc_inform("CLASS: realizing class '%s'%s %p %p #%u %s%s",
cls->nameForLogging(), isMeta ? " (meta)" : "",
(void*)cls, ro, cls->classArrayIndex(),
cls->isSwiftStable() ? "(swift)" : "",
cls->isSwiftLegacy() ? "(pre-stable swift)" : "");
}
//初始化父类ro、rw,这是一个循环父类的父类
supercls = realizeClassWithoutSwift(remapClass(cls->getSuperclass()), nil);
//初始化元类ro、rw,这是一个循环,元类的元类
metacls = realizeClassWithoutSwift(remapClass(cls->ISA()), nil);
#if SUPPORT_NONPOINTER_ISA
if (isMeta) {
cls->setInstancesRequireRawIsa();
} else {
if (DisableNonpointerIsa) {
instancesRequireRawIsa = true;
}
else if (!hackedDispatch && 0 == strcmp(ro->getName(), "OS_object"))
{
hackedDispatch = true;
instancesRequireRawIsa = true;
}
else if (supercls && supercls->getSuperclass() &&
supercls->instancesRequireRawIsa())
{
instancesRequireRawIsa = true;
rawIsaIsInherited = true;
}
if (instancesRequireRawIsa) {
cls->setInstancesRequireRawIsaRecursively(rawIsaIsInherited);
}
}
// SUPPORT_NONPOINTER_ISA
#endif
//关联父类以及元类
cls->setSuperclass(supercls);
cls->initClassIsa(metacls);
if (supercls && !isMeta) reconcileInstanceVariables(cls, supercls, ro);
cls->setInstanceSize(ro->instanceSize);
if (ro->flags & RO_HAS_CXX_STRUCTORS) {
cls->setHasCxxDtor();
if (! (ro->flags & RO_HAS_CXX_DTOR_ONLY)) {
cls->setHasCxxCtor();
}
}
if ((ro->flags & RO_FORBIDS_ASSOCIATED_OBJECTS) ||
(supercls && supercls->forbidsAssociatedObjects()))
{
rw->flags |= RW_FORBIDS_ASSOCIATED_OBJECTS;
}
if (supercls) {
addSubclass(supercls, cls);
} else {
addRootClass(cls);
}
//初始化属性、方法、协议,以及分类的方法
methodizeClass(cls, previously);
return cls;
}
分析:realizeClassWithoutSwift主要作用如下
rw,ro初始化:类、父类、元类,根据isa走位图都会初始化- 类
关联上父类以及元类 - 初始化
属性、方法、协议,以及分类的方法
2.2 getMethodNoSuper_nolock
static method_t *
getMethodNoSuper_nolock(Class cls, SEL sel)
{
runtimeLock.assertLocked();
ASSERT(cls->isRealized());
auto const methods = cls->data()->methods();
for (auto mlists = methods.beginLists(),
end = methods.endLists();
mlists != end;
++mlists)
{
// search_method_list 的调用者,将其内联将 getMethodNoSuper_nolock 转换为无框架函数,并从此代码路径中消除任何存储。
method_t *m = search_method_list_inline(*mlists, sel);
if (m) return m;
}
return nil;
}
分析:getMethodNoSuper_nolock主要是获取类中所有方法然后把方法列表和要查找的sel放入函数search_method_list_inline中,跟进此方法,发现极有可能调用了排序方法findMethodInSortedMethodList,这个方法才是最终返回SEL对应IMP的方法。
2.3 findMethodInSortedMethodList
static method_t *
findMethodInSortedMethodList(SEL key, const method_list_t *list, const getNameFunc &getName)
{
ASSERT(list);
auto first = list->begin(); //第一个method位置,首地址
auto base = first;
decltype(first) probe;
//把key直接转换成uintptr_t 因为修复过后的method_list_t中的元素是排过序的
uintptr_t keyValue = (uintptr_t)key;
uint32_t count;
//count >>= 1 count右移1位相当于count/2取整,如8>>1=4,7>>1=3
//假设count=8,keyvalue=7
//第一次 count=8 base=0 probe=base+4
//第二次 count=count>>1=7>>1=3 probe=base+5+(count>>1)=base+5+1
for (count = list->count; count != 0; count >>= 1) {
probe = base + (count >> 1);
uintptr_t probeValue = (uintptr_t)getName(probe);
if (keyValue == probeValue) {
//分类覆盖,分类中有相同名字的方法,如果有分类的方法我们就获取分类的方法
while (probe > first && keyValue == (uintptr_t)getName((probe - 1))) {
probe--;
}
return &*probe;
}
if (keyValue > probeValue) {
//第一次 base=probe+1=base+4+1=base+5;
//第二次 base=base+5+1+1=base+7
base = probe + 1;
count--;
}
}
return nil;
}
分析:
- 方法列表中
sel大小是进过排序的,二分法循环查找方法列表中SEL与目标SEL是否相等 - 二分查找就是每次取
中间值probeValue跟keyvalue进行比较,相同就返回,不相同继续二分查找
2.4 二分查找示例图
假设方法列表count为8,用
伪代码实现一下二分法查找IMP流程
int keyValue=1;
int first=0;
int base=first;
int count;
int probe=0;
//假设count=8
for(count=8;count!=0;count=count/2){
probe=base+(count/2);
if(keyValue==probe){
while(probe >first && keyValue ==(probe - 1)){
probe--;
}
return probe;
}
if(keyValue>probe){
base=probe+1;
count--;
}
}
总结
方法的本质是消息发送,objc_msgSend从缓存中通过SEL快速查找IMP缓存中快速查找不到,会进入lookUpImpOrForward慢速查找,在本类中二分法查找,如果找不到就在父类的缓存中循环查找。- 如果本类和父类都没有查询到,此时系统会给你一次机会,判断是否执行过
动态方法决议,如果没有则走动态方法决议。