在iOS底层之objc_msgSend快速查找流程里分析里调用方法的本质,就是消息发送,查找类的方法缓存,那么如果经历CacheLookup
后没找到缓存,即快速查找流程找不到,则会开始慢速查找,从methodList
查找,这一篇文章我们来分析方法的查找流程。
在CacheLookup
快速查找流程中,当没有找到方法imp
缓存,无论是走到CheckMiss
还是JumpMiss
,最终都会走到__objc_msgSend_uncached
汇编函数。其定义是:
STATIC_ENTRY __objc_msgSend_uncached
UNWIND __objc_msgSend_uncached, FrameWithNoSaves
// THIS IS NOT A CALLABLE C FUNCTION
// Out-of-band r10 is the searched class
// r10 is already the class to search
MethodTableLookup NORMAL // r11 = IMP
jmp *%r11 // goto *imp
END_ENTRY __objc_msgSend_uncached
可以看到关键代码MethodTableLookup
,会查找方法表。
其定义是:
.macro MethodTableLookup
// push frame
SignLR
stp fp, lr, [sp, #-16]!
mov fp, sp
// save parameter registers: x0..x8, q0..q7
sub sp, sp, #(10*8 + 8*16)
stp q0, q1, [sp, #(0*16)]
stp q2, q3, [sp, #(2*16)]
stp q4, q5, [sp, #(4*16)]
stp q6, q7, [sp, #(6*16)]
stp x0, x1, [sp, #(8*16+0*8)]
stp x2, x3, [sp, #(8*16+2*8)]
stp x4, x5, [sp, #(8*16+4*8)]
stp x6, x7, [sp, #(8*16+6*8)]
str x8, [sp, #(8*16+8*8)]
// 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 registers and return
ldp q0, q1, [sp, #(0*16)]
ldp q2, q3, [sp, #(2*16)]
ldp q4, q5, [sp, #(4*16)]
ldp q6, q7, [sp, #(6*16)]
ldp x0, x1, [sp, #(8*16+0*8)]
ldp x2, x3, [sp, #(8*16+2*8)]
ldp x4, x5, [sp, #(8*16+4*8)]
ldp x6, x7, [sp, #(8*16+6*8)]
ldr x8, [sp, #(8*16+8*8)]
mov sp, fp
ldp fp, lr, [sp], #16
AuthenticateLR
.endmacro
其中关键代码是_lookUpImpOrForward
。为什么?
通过调用一个实例方法[person sayHello]
,并打断点。打开汇编调试查看:
可以看到汇编停在了
[person sayHello]
调用之前,下一步开始发送消息调用sayHello
。打断点objc_msgSend
,按住control+点击step into
进入内部调用。_objc_msgSend_uncached
之上完成了类信息获取,方法快速查找流程,没找到缓存时来到了_objc_msgSend_uncached
,断点跳到这一句,继续step into
进入内部。可以看到来到了lookUpImpOrForward函数,显示这个函数在
objc-runtime-new.mm
文件的6099
行。也就验证了上面我们说的
_objc_msgSend_uncached
之后会来到_lookUpImpOrForward
。
通过汇编源码查找
C/C++
源码的技巧
例如_lookUpImpOrForward
,
汇编中查找C/C++
方法时,需要将汇编调用的方法_lookUpImpOrForward
去掉一个下划线
从_objc_msgSend_uncached过来时behavior 是LOOKUP_INITIALIZE | LOOKUP_RESOLVER,该枚举定义是
/* method lookup */
enum {
LOOKUP_INITIALIZE = 1,
LOOKUP_RESOLVER = 2,
LOOKUP_CACHE = 4,
LOOKUP_NIL = 8,
};
搜索找到lookUpImpOrForward
的定义,主要的步骤分析我都写在注释里了。
IMP lookUpImpOrForward(id inst, SEL sel, Class cls, int behavior)
{
//_objc_msgForward_impcache获取消息转发的函数地址,用于方法未找到并且动态方法决议未处理时调用。如果消息转发也不成功,则程序会抛出方法未识别的错误
const IMP forward_imp = (IMP)_objc_msgForward_impcache;
IMP imp = nil;
Class curClass;
runtimeLock.assertUnlocked();
// Optimistic cache lookup
//当behavior&LOOKUP_CACHE==1时,查找下缓存。为了防止多线程操作时,调用函数时,此时另一线程缓存进来了,可以快速查找一遍缓存,因为下面的慢速查找很耗时
//1.当从MethodTableLookup过来时,behavior=3,3&4=0为假,往下执行
//2.动态方法决议回来第一次时'sel=resolveInstanceMethod',此时behavior=12 12&4=4为真,这时还未缓存resolveInstanceMethod,imp为空,往下执行)
//3. 动态方法决议回来第二次时'sel=say666',此时behavior=12, 12&4=4为真,无论动态方法决议流程+resolveInstanceMethod里是否正确添加了imp,此时缓存中依旧为空,往下执行
//4.动态方法决议处理后,会从resolveMethod_locked的lookUpImpOrForward重新进来一次,此时behavior=5,5&4=4为真,此时找到的imp为动态方法决议第二次进来时缓存的say666的方法地址,跳转done_nolock(不缓存)
if (fastpath(behavior & LOOKUP_CACHE)) {
imp = cache_getImp(cls, sel);
if (imp) goto done_nolock;
}
//在isRealized和isInitialized检查期间保持锁,防止对并发实现的竞争
//runtimeLock在方法搜索过程中被保持
//方法查找+缓存填充相对于方法添加而言是原子的
//保证方法查找过程中method-lookup + cache-fill中方法添加的原子性。
// 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();
// TODO: this check is quite costly during process startup.
//检查是否是已知类(已经加载的类)
//确保这个类是通过objc_duplicateClass, objc_initializeClassPair或objc_allocateClassPair合法注册的,或者内置到二进制文件中的,而不是创建一个看起来像类而实际并不是的二进制的blob,做CFI攻击
checkIsKnownClass(cls);
//小概率情况下,当前类未实现时
//如果没有,需要先实现,目的是为了能确定父类继承链和元类继承链,后面查找递归,当前类没找到,则需要从继承链查找
//内部有双向链表会更新子类和父类
//还会把类的相关属性协议方法贴到rw.ext()中
if (slowpath(!cls->isRealized())) {
cls = realizeClassMaybeSwiftAndLeaveLocked(cls, runtimeLock);
// runtimeLock may have been dropped but is now locked again
}
//判断如果类没有初始化,则会递归父类继承链执行初始化所有的类
//没实现时会callInitialize对类发送消息调用initialize方法
//所以侧面说明了initialize和load方法一样,不需要主动调用,底层实现时帮你调用
if (slowpath((behavior & LOOKUP_INITIALIZE) && !cls->isInitialized())) {
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
}
runtimeLock.assertLocked();
curClass = cls;
// The code used to lookpu 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().
//重点!!!!从这里对imp赋值
for (unsigned attempts = unreasonableClassCount();;) {
// curClass method list.
//查找curClass方法列表,如果有,就不需要去找curClass的父类了
//1.当动态方法决议回来第一次时'sel=resolveInstanceMethod',如果curClass(或者递归的父类链)方法列表实现了,则赋值imp,执行done
//2.当动态方法决议回来第二次时'sel=say666',此时curClass的方法列表可以找到imp,跳到done
Method meth = getMethodNoSuper_nolock(curClass, sel);
//如果找到了,则跳转到done(缓存方法、解锁)
if (meth) {
imp = meth->imp;
goto done;
}
//本类没找到,则将curClass赋值为当前类的父类
//如果父类是nil,也就是继承链走完了,那么会imp = forward_imp走消息转发,跳出循环
if (slowpath((curClass = curClass->superclass) == 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.
//查找当前类的缓存,注意,上面已经将curClass父类赋值给当前类,所以是查找父类的缓存,并不是递归当前类了
//查找过程是`cache_getImp` - `lookup` - `lookUpImpOrForward`,也是通过`cache_getImp`查找缓存,不过参数是`GETIMP`,跟上面NORMAL区别是找不到缓存时,不是走`_objc_msgSend_uncached`,而是走`LGetImpMiss`,最后会返回nil,避免了导致死循环。
imp = cache_getImp(curClass, sel);
//上面当父类链的父类为nil时,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,则跳转done(缓存方法、解锁),缓存在curClass(当次循环中curClasss是入参类则缓存到入参类,是某级父类就缓存到某级父类中)
//1.当动态方法决议回来第一次时'sel=resolveInstanceMethod',这时候从cache找的imp是空的,不走进这里。
if (fastpath(imp)) {
// Found the method in a superclass. Cache it in this class.
goto done;
}
//当前父类没有找到,由于for循环没有跳出的条件判断,会一直死循环查找父类继承链,直到break
}
// No implementation found. Try method resolver once.
//这一步代表了流程只走一次下面这段代码(也就是只会走一次动态方法决议),因为 behavior ^= LOOKUP_RESOLVER改变了behavior,与条件behavior & LOOKUP_RESOLVER = 1不能同时满足
//LOOKUP_RESOLVER =2,behavior & LOOKUP_RESOLVER也就是3&2=2,执行条件语句代码块
//1.从CachLookup后进来behavior=3,3&2为真
//2.从动态方法决议回来第一次:12,此时sel为resolveInstanceMethod,12&2为假
//3.从动态方法决议回来第二次:12,此时sel为say666,12&2为假
//也就是保证了动态方法决议只会被执行一次
if (slowpath(behavior & LOOKUP_RESOLVER)) {
//behavior = 3^2 = 1
behavior ^= LOOKUP_RESOLVER;
//没有找到imp,则开始走本类的动态方法决议
return resolveMethod_locked(inst, sel, cls, behavior);
}
done:
//缓存方法
//当从入参类或者父类链中找到imp,则缓存sel和imp到当前循环中的curClass中。1.当从_objc_msgSend_uncached通过NORMAL进来直接在入参类/父类继承链的方法列表中查找到imp时,此时sel和imp均为say666 1.如果是动态方法决议第一次进来,sel='resolveInstanceMethod',imp为resolveInstanceMethod的实现地址 2.如果是动态方法决议第二次进来,此时sel='say666',imp为动态方法决议添加的say666的方法地址
log_and_fill_cache(cls, imp, sel, inst, curClass);
//解锁
runtimeLock.unlock();
done_nolock:
//当动态方法决议处理完成,从resolveMethod_locked的lookUpImpOrForward重新进来,此时behavior=5,5&8=0为假,执行最后的return imp。此时的imp是动态方法决议第二次进来时缓存的say666的方法地址。就这样如果动态方法决议阶段+resolveInstanceMethod里正确添加了sel的imp,那在这一步就找到了imp
if (slowpath((behavior & LOOKUP_NIL) && imp == forward_imp)) {
return nil;
}
//当动态方法决议整个流程都没找到imp时,那么此时的imp就是forward_imp,也就是_objc_msgForward_impcache,回到MethodTableLookup,把imp的值给x17,接着TailCallFunctionPointer x17,也就是调用默认的转发处理_objc_msgForward_impcache这个方法,进入消息转发流程。如果依然未处理,则抛出消息未识别的错误,终止进程。
return imp;
}
主要步骤是:
【第一步】由于多线程可能导致方法缓存在另外线程添加,为了避免下面慢速查找继承链方法列表需要消耗大量时间,所以再次从
cache
缓存中进行查找,即快速查找
,找到则直接返回imp
,否则,则进入【第二步】-
【第二步】判断
cls
是不是已知类如果不是,则
报错
类是否已实现,如果没有,则需要先实现,确定其父类链或元类继承链,此时实例化的目的是为了确定父类链、
ro
、以及rw
等,方法后续数据的读取以及查找需要是否已初始化
initialize
,如果没有,则初始化
-
【第三步】
for循环
,按照类继承链 或者 元类继承链
的顺序查找当前类的方法列表中使用
二分查找算法
查找方法,如果找到,则将方法写入cache
(在iOS底层之cache_t探究中分析了写入过程),并返回imp
,如果没有找到,则返回nil
当前
cls
被赋值为父类,如果父类等于nil
,则imp = forward_imp
,并跳出循环,进入【第四步】如果父类链中存在循环,则报错,终止循环
-
从父类缓存中查找方法
如果未找到,进入下一次循环,
Method meth = getMethodNoSuper_nolock(curClass, sel);
,curClass变为其父类,查找父类方法列表如果找到,则直接返回
imp
,执行cache
写入流程
-
【第四步】判断是否执行过
动态方法决议
- 如果没有,执行
动态方法决议
- 如果没有,执行
其中部分函数的源码
- 二分法查找方法列表的imp
-
getMethodNoSuper_nolock
查找当前类的方法列表
static method_t *
getMethodNoSuper_nolock(Class cls, SEL sel)
{
runtimeLock.assertLocked();
ASSERT(cls->isRealized());
// fixme nil cls?
// fixme nil sel?
//查找data里的methods() 方法列表
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;
}
ALWAYS_INLINE static method_t *
search_method_list_inline(const method_list_t *mlist, SEL sel)
{
int methodListIsFixedUp = mlist->isFixedUp();
int methodListHasExpectedSize = mlist->entsize() == sizeof(method_t);
if (fastpath(methodListIsFixedUp && methodListHasExpectedSize)) {
return findMethodInSortedMethodList(sel, mlist);
} else {
// Linear search of unsorted method list
for (auto& meth : *mlist) {
if (meth.name == sel) return &meth;
}
}
#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, mlist);
以二分法查找
ALWAYS_INLINE static method_t *
findMethodInSortedMethodList(SEL key, const method_list_t *list)
{
//list为data()中的方法列表
ASSERT(list);
const method_t * const first = &list->first;
//取base为第一个元素
const method_t *base = first;
const method_t *probe;
//key为传进来的cmd,也就是我们调用的sayHello
uintptr_t keyValue = (uintptr_t)key;
uint32_t count;
//count赋值为列表元素个数,每一次循环都将count/2
for (count = list->count; count != 0; count >>= 1) {
//base为二分法区间的最小元素。将base平移count/2,也就是平移到列表中间的位置,probe为中间的元素
probe = base + (count >> 1);
//获取probe存储的名字,也就是sel方法名
uintptr_t probeValue = (uintptr_t)probe->name;
//如果查找的cmd等于sel
if (keyValue == probeValue) {
// `probe` is a match.
// Rewind looking for the *first* occurrence of this value.
// This is required for correct category overrides.
//由于分类的重写的方法加载到内存中是会插在类的同名方法前面,所以这里需要循环向前一个查找sel,如果有,则返回分类的sel。
//如果存在多个分类重写,则看哪个分类先加载
while (probe > first && keyValue == (uintptr_t)probe[-1].name) {
probe--;
}
return (method_t *)probe;
}
//如果cmd大于sel,则需要往后面查找,赋值最小值base为中间值probe+1,如果cmd小于sel,则不走进来
if (keyValue > probeValue) {
base = probe + 1;
count--;
}
//没找到一致的sel,则进入下一次循环,再取base和count的中间值
}
//如果循环到count==0,也没找到,则返回nil
return nil;
}
其执行流程图如下
- 查找父类
imp
源码:
_cache_getImp入口-> CacheLookup (GETIMP)
STATIC_ENTRY _cache_getImp
GetClassFromIsa_p16 p0
CacheLookup GETIMP, _cache_getImp
LGetImpMiss:
mov p0, #0
ret
END_ENTRY _cache_getImp
开始CacheLookup (GETIMP),如果命中则CacheHit,未命中则CheckMiss 或者JumpMiss ->
.macro CacheLookup
//
// Restart protocol:
//
// As soon as we're past the LLookupStart$1 label we may have loaded
// an invalid cache pointer or mask.
//
// When task_restartable_ranges_synchronize() is called,
// (or when a signal hits us) before we're past LLookupEnd$1,
// then our PC will be reset to LLookupRecover$1 which forcefully
// jumps to the cache-miss codepath which have the following
// requirements:
//
// GETIMP:
// The cache-miss is just returning NULL (setting x0 to 0)
//
// NORMAL and LOOKUP:
// - x0 contains the receiver
// - x1 contains the selector
// - x16 contains the isa
// - other registers are set as per calling conventions
//
LLookupStart$1:
// p1 = SEL, p16 = isa
ldr p11, [x16, #CACHE] // p11 = mask|buckets
#if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16
and p10, p11, #0x0000ffffffffffff // p10 = buckets
and p12, p1, p11, LSR #48 // x12 = _cmd & mask
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_LOW_4
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
add p12, p10, p12, LSL #(1+PTRSHIFT)
// p12 = buckets + ((_cmd & mask) << (1+PTRSHIFT))
ldp p17, p9, [x12] // {imp, sel} = *bucket
1: cmp p9, p1 // if (bucket->sel != _cmd)
b.ne 2f // scan more
CacheHit $0 // call or return imp
2: // not hit: p12 = not-hit bucket
CheckMiss $0 // miss if bucket->sel == 0
cmp p12, p10 // wrap if bucket == buckets
b.eq 3f
ldp p17, p9, [x12, #-BUCKET_SIZE]! // {imp, sel} = *--bucket
b 1b // loop
3: // wrap: p12 = first bucket, w11 = mask
#if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16
add p12, p12, p11, LSR #(48 - (1+PTRSHIFT))
// p12 = buckets + (mask << 1+PTRSHIFT)
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_LOW_4
add p12, p12, p11, LSL #(1+PTRSHIFT)
// p12 = buckets + (mask << 1+PTRSHIFT)
#else
#error Unsupported cache mask storage for ARM64.
#endif
// Clone scanning loop to miss instead of hang when cache is corrupt.
// The slow path may detect any corruption and halt later.
ldp p17, p9, [x12] // {imp, sel} = *bucket
1: cmp p9, p1 // if (bucket->sel != _cmd)
b.ne 2f // scan more
CacheHit $0 // call or return imp
2: // not hit: p12 = not-hit bucket
CheckMiss $0 // miss if bucket->sel == 0
cmp p12, p10 // wrap if bucket == buckets
b.eq 3f
ldp p17, p9, [x12, #-BUCKET_SIZE]! // {imp, sel} = *--bucket
b 1b // loop
LLookupEnd$1:
LLookupRecover$1:
3: // double wrap
JumpMiss $0
.endmacro
CheckMiss / JumpMiss -> LGetImpMiss,则执行LGetImpMiss
.macro CheckMiss
// miss if bucket->sel == 0
.if $0 == GETIMP
cbz p9, LGetImpMiss
.elseif $0 == NORMAL
cbz p9, __objc_msgSend_uncached
.elseif $0 == LOOKUP
cbz p9, __objc_msgLookup_uncached
.else
.abort oops
.endif
.endmacro
.macro JumpMiss
.if $0 == GETIMP //走这个分支
b LGetImpMiss
.elseif $0 == NORMAL
b __objc_msgSend_uncached
.elseif $0 == LOOKUP
b __objc_msgLookup_uncached
.else
.abort oops
.endif
.endmacro
其流程图:
- 如果父类链没找到
imp
,则进入动态方法决议resolveMethod_locked
static NEVER_INLINE IMP
resolveMethod_locked(id inst, SEL sel, Class cls, int behavior)
{
runtimeLock.assertLocked();
ASSERT(cls->isRealized());
//lookUpImpOrForward没找到imp,再给你一次机会,去处理
runtimeLock.unlock();
//如果不是元类
if (! cls->isMetaClass()) {
// try [cls resolveInstanceMethod:sel]
//直接走当前类的动态方法决议
resolveInstanceMethod(inst, sel, cls);
}
else {
// try [nonMetaClass resolveClassMethod:sel]
// and [cls resolveInstanceMethod:sel]
//元类,走元类的动态方法决议
resolveClassMethod(inst, sel, cls);
//再查找一次methodList,如果还是nil,会查找一次当前类的动态方法决议
if (!lookUpImpOrNil(inst, sel, cls)) {
//走当前类的动态方法决议
resolveInstanceMethod(inst, sel, cls);
}
}
// chances are that calling the resolver have populated the cache
// so attempt using it
//上面给你机会去处理,现在重新找一次lookUpImpOrForward,此时behavior | LOOKUP_CACHE = 1|4 = 5
return lookUpImpOrForward(inst, sel, cls, behavior | LOOKUP_CACHE);
}
* resolveInstanceMethod
* Call +resolveInstanceMethod, looking for a method to be added to class cls.
* cls may be a metaclass or a non-meta class.
* Does not check if the method already exists.
**********************************************************************/
static void resolveInstanceMethod(id inst, SEL sel, Class cls)
{
runtimeLock.assertUnlocked();
ASSERT(cls->isRealized());
SEL resolve_sel = @selector(resolveInstanceMethod:);
//如果resolveInstanceMethod方法未实现,则直接退出,这一步会触发一次查找lookUpImpOrNil-> lookUpImpOrForward,这时的behavior为0|LOOKUP_CACHE | LOOKUP_NIL=4|8=12
if (!lookUpImpOrNil(cls, resolve_sel, cls->ISA())) {
// Resolver not implemented.
return;
}
BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
bool resolved = msg(cls, resolve_sel, sel);
// Cache the result (good or bad) so the resolver doesn't fire next time.
// +resolveInstanceMethod adds to self a.k.a. cls
IMP imp = lookUpImpOrNil(inst, sel, cls);
if (resolved && PrintResolving) {
if (imp) {
_objc_inform("RESOLVE: method %c[%s %s] "
"dynamically resolved to %p",
cls->isMetaClass() ? '+' : '-',
cls->nameForLogging(), sel_getName(sel), imp);
}
else {
// Method resolver didn't add anything?
_objc_inform("RESOLVE: +[%s resolveInstanceMethod:%s] returned YES"
", but no new implementation of %c[%s %s] was found",
cls->nameForLogging(), sel_getName(sel),
cls->isMetaClass() ? '+' : '-',
cls->nameForLogging(), sel_getName(sel));
}
}
}
看看lookUpImpOrNil
的实现
static inline IMP
lookUpImpOrNil(id obj, SEL sel, Class cls, int behavior = 0)
{
return lookUpImpOrForward(obj, sel, cls, behavior | LOOKUP_CACHE | LOOKUP_NIL);
}
先调用了resolveInstanceMethod
去看看resolver
中帮没帮实现,如果帮实现了,在走lookUpImpOrNil
过程中就会把方法缓存起来。然后会回到resolveMethod_locked
方法中调用lookUpImpOrForward(inst, sel, cls, behavior | LOOKUP_CACHE)
,这时可以从缓存找到imp,进入done_nolock
, 返回方法的imp
;如果整个慢速查找流程(包括动态方法决议)都没有找到,就会返回存着forward_imp
的imp
。然后就又会回到MethodTableLookup
,把imp
的值给x17
,接着TailCallFunctionPointer x17
,调用forward_imp
也就是_objc_msgForward_impcache
这个方法,进去消息转发流程。
元类的动态方法决议流程和上面类实例的流程差不多,这里不再做分析。resolveClassMethod的实现是
* resolveClassMethod
* Call +resolveClassMethod, looking for a method to be added to class cls.
* cls should be a metaclass.
* Does not check if the method already exists.
**********************************************************************/
static void resolveClassMethod(id inst, SEL sel, Class cls)
{
runtimeLock.assertUnlocked();
ASSERT(cls->isRealized());
ASSERT(cls->isMetaClass());
if (!lookUpImpOrNil(inst, @selector(resolveClassMethod:), cls)) {
// Resolver not implemented.
return;
}
Class nonmeta;
{
mutex_locker_t lock(runtimeLock);
nonmeta = getMaybeUnrealizedNonMetaClass(cls, inst);
// +initialize path should have realized nonmeta already
if (!nonmeta->isRealized()) {
_objc_fatal("nonmeta class %s (%p) unexpectedly not realized",
nonmeta->nameForLogging(), nonmeta);
}
}
BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
bool resolved = msg(nonmeta, @selector(resolveClassMethod:), sel);
// Cache the result (good or bad) so the resolver doesn't fire next time.
// +resolveClassMethod adds to self->ISA() a.k.a. cls
IMP imp = lookUpImpOrNil(inst, sel, cls);
if (resolved && PrintResolving) {
if (imp) {
_objc_inform("RESOLVE: method %c[%s %s] "
"dynamically resolved to %p",
cls->isMetaClass() ? '+' : '-',
cls->nameForLogging(), sel_getName(sel), imp);
}
else {
// Method resolver didn't add anything?
_objc_inform("RESOLVE: +[%s resolveClassMethod:%s] returned YES"
", but no new implementation of %c[%s %s] was found",
cls->nameForLogging(), sel_getName(sel),
cls->isMetaClass() ? '+' : '-',
cls->nameForLogging(), sel_getName(sel));
}
}
}
为什么查找元类方法时在执行完_class_resolveClassMethod之后会再次执行一次_class_resolveInstanceMethod?
比如有一个类Person,我们查找 Person 的一个类方法,如果没找到,会继续找他的第一个元类,再找不到,会继续找根元类 ,最终会找到 NSObject。
Person(类方法) 找——> 元类(实例方法) 找——> 根元类(实例方法) 找——> NSObject(实例方法)
实例方法存在类对象里面,类方法存在元类里面。
当遍历元类继承链后都没找到时,就找一遍NSObject的实例方法列表。
- 下面来看看
_objc_msgForward_impcache
做了什么__objc_msgForward_impcache
STATIC_ENTRY __objc_msgForward_impcache
// No stret specialization.
//进入__objc_msgForward
b __objc_msgForward
END_ENTRY __objc_msgForward_impcache
ENTRY __objc_msgForward
//把存有__objc_forward_handler的页存入X17
adrp x17, __objc_forward_handler@PAGE
//把x17向后偏移了__objc_forward_handler@PAGEOFF,然后把X17中地址的值存入p17,
ldr p17, [x17, __objc_forward_handler@PAGEOFF]
TailCallFunctionPointer x17
END_ENTRY __objc_msgForward
TailCallFunctionPointer
是个方法指针,调用了上面的__objc_forward_handler
.macro TailCallFunctionPointer
// $0 = function pointer value
br $0
.endmacro
由于查找__objc_forward_handler
未找到汇编定义,去掉一个_
查找C/C++
源码:
// Default forward handler halts the process.
__attribute__((noreturn, cold)) void
objc_defaultForwardHandler(id self, SEL sel)
{
_objc_fatal("%c[%s %s]: unrecognized selector sent to instance %p "
"(no message forward handler is installed)",
class_isMetaClass(object_getClass(self)) ? '+' : '-',
object_getClassName(self), sel_getName(sel), self);
}
void *_objc_forward_handler = (void*)objc_defaultForwardHandler;
可以看到是由objc_defaultForwardHandler
定义的。而这个函数里面,我们看到了熟悉的方法选择器未找到的打印信息。从打印信息的组成class_isMetaClass(object_getClass(self)) ? '+' : '-'
,可以看到,系统内部并没有区分方法是实例方法还是类方法,而是通过是否是元类,来区分打印方法类型。
其流程图:
然而在报错之前,还会对消息进行转发和方法重签名调用,再次挽救。加上动态方法决议,共有3
次挽救机会。
这个流程留待下一节分析。
总结
查找实例方法
,会在类
中查找,慢速查找的继承链是:类->父类->根类->nil
同理,查找类方法
,则在元类
中查找,其慢速查找的链是:元类->根元类->根类->nil
如果objc_msgSend
快速查找缓存,慢速查找当前类方法列表、父类的缓存和方法列表都没有找到imp
方法实现,则尝试动态方法决议
如果动态方法决议
仍然没有找到,则进行消息转发
如果消息转发
还没有处理,则进去方法签名处理调用
。
如果方法签名处理调用
还未处理,则程序会抛出错误。