引入
通过测试代码演示方法查找流程:
isa和superclass走向流程图:
1.LGPerson实例方法 只实现不声明 在NSObject分类中只声明不实现 正常运行 调用LGPerson的实例方法实现.
//NSObject分类.m
- (void)sayEasy{
NSLog(@"%s",__func__);
}
//调用
[student performSelector: @selector(sayEasy)];
调用NSObject的- (void)sayEasy方法 正常运行。 2.LGPerson类方法 只声明不实现 在NSObject分类中只实现不声明 正常运行 调用NSObject分类中实现.
//NSObject分类.m
- (void)sayEasy{
NSLog(@"%s",__func__);
}
+ (void)sayEasy{
NSLog(@"%s",__func__);
}
//调用 方法未声明,通过performSelector方式调用
[LGStudent performSelector: @selector(sayEasy)];
- 如果根元类+ (void)sayEasy实现了,则会调用根元类+ (void)sayEasy;
- 如果根元类+ (void)sayEasy没有实现,会沿着superclass找到NSObject类,会调用NSObject类的- (void)sayEasy。
objc_msgSend 慢速查找
在objc_msgSend消息流程快速查找中我们分析了
快速查找流程
,如果快速查不到,则需要进入慢速查找流程
。 在快速查找流程中,如果没有找到方法实现,无论是走到CheckMiss
还是JumpMiss
,最终都会走到__objc_msgSend_uncached
汇编函数.
__objc_msgSend_uncached
- 在
objc-msg-arm64.s
文件中查找__objc_msgSend_uncached
的汇编实现,其中的核心是MethodTableLookup(即查询方法列表)
,其源码如下:STATIC_ENTRY __objc_msgSend_uncached UNWIND __objc_msgSend_uncached, FrameWithNoSaves // THIS IS NOT A CALLABLE C FUNCTION // Out-of-band p16 is the class to search MethodTableLookup // 开始查询方法列表 TailCallFunctionPointer x17 END_ENTRY __objc_msgSend_uncached
- 搜索
MethodTableLookup
的汇编实现,其中的核心是_lookUpImpOrForward
,汇编源码实现如下:
.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
测试代码验证上述汇编流程
//LGPerson.h
- (void)say666;
//LGPerson.m
//- (void)say666 未实现
//main.m
LGPerson *person = [LGPerson alloc];
[person say666];
- 在源码测试代码
[person say666]
打断点,打开汇编调试【Debug -- Debug worlflow -- 勾选Always show Disassembly】
- 汇编中
objc_msgSend
加一个断点,执行断住,按住control + stepinto
,进入objc_msgSend
的汇编 - 在
_objc_msgSend_uncached
加一个断点,执行断住,按住control + stepinto
,进入汇编。 从上可以看出最后走到的就是lookUpImpOrForward
,此时并不是汇编实现。
源码方法查找:
汇编调用c/c++方法,c/c++方法实现里少一个下划线;反之加下划线。
所以源码里全局搜索lookUpImpOrForward
实现,来到慢速查找的c/c++部分。在objc-runtime-new.mm
文件中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();
// 快速查找,如果找到则直接返回imp
//目的:防止多线程操作时,刚好调用函数,此时缓存进来了
if (fastpath(behavior & LOOKUP_CACHE)) {
imp = cache_getImp(cls, sel);
if (imp) goto done_nolock;
}
//加锁,目的是保证读取的线程安全
runtimeLock.lock();
//判断是否是一个已知的类:判断当前类是否是已经被认可的类,即已经加载的类
checkIsKnownClass(cls);
//判断类是否实现,如果没有,需要先实现,此时的目的是为了确定父类链,方便后续的for循环
if (slowpath(!cls->isRealized())) {
cls = realizeClassMaybeSwiftAndLeaveLocked(cls, runtimeLock);//详细见下方单独说明
}
//判断类是否初始化,如果没有,递归初始化所有的类
if (slowpath((behavior & LOOKUP_INITIALIZE) && !cls->isInitialized())) {
//initializeAndLeaveLocked递归初始化所有的类,调用callInitialize方法。callInitialize方法是给cls发送initialize
cls = initializeAndLeaveLocked(cls, inst, runtimeLock);
}
runtimeLock.assertLocked();
curClass = cls;
//----下面for循环是我们研究的重心
// unreasonableClassCount -- 表示类的迭代的上限
//(猜测这里递归的原因是attempts在第一次循环时作了减一操作,然后再次循环时,仍在上限的范围内,所以可以继续递归)
for (unsigned attempts = unreasonableClassCount();;) {
//---当前类方法列表(采用二分查找算法),如果找到,则返回,将方法缓存到cache中
Method meth = getMethodNoSuper_nolock(curClass, sel);//详细见下方单独说明
if (meth) {
imp = meth->imp;
goto done;
}
//当前类 = 当前类的父类,并判断父类是否为nil
if (slowpath((curClass = curClass->superclass) == nil)) {
//--未找到方法实现,方法解析器也不行,使用转发
imp = forward_imp;
break;
}
// 如果父类链中存在循环,则停止
if (slowpath(--attempts == 0)) {
_objc_fatal("Memory corruption in class list.");
}
// --父类缓存
imp = cache_getImp(curClass, sel);//详细见下方单独说明
if (slowpath(imp == forward_imp)) {
// 如果在父类缓存中找到了imp,imp是forward消息转发,则停止查找,且不缓存,首先调用此类的方法解析器
break;
}
if (fastpath(imp)) {
//如果在父类缓存中,找到了此方法,将其存储到cache中
goto done;
}
}
//没有找到方法实现,尝试一次方法解析
if (slowpath(behavior & LOOKUP_RESOLVER)) {
//动态方法决议的控制条件,表示流程只走一次
//^异或操作 相同为0,不同为1
behavior ^= LOOKUP_RESOLVER;
return resolveMethod_locked(inst, sel, cls, behavior);
}
// 顺序走到这 说明找到imp了
done:
//存储到缓存
log_and_fill_cache(cls, imp, sel, inst, curClass);
//解锁
runtimeLock.unlock();
done_nolock:
if (slowpath((behavior & LOOKUP_NIL) && imp == forward_imp)) {
return nil;
}
return imp;
}
整体的慢速查找流程如图所示:
lookUpImpOrForward
lookUpImpOrForward
里初始:
IMP forward_imp = (IMP)_objc_msgForward_impcache;
IMP imp = nil;
主要有以下几步:
-
【第一步】当前类
cache
缓存中cache_getImp
查找,即快速查找
,找到则直接返回imp
,反之,则进入【第二步】 -
【第二步】判断
cls
checkIsKnownClass
判断是否是已知类
,如果不是,则报错
- 类是否
实现
,如果没有,则需要realizeClassMaybeSwiftAndLeaveLocked
先实现,确定其父类链,此时实例化的目的是为了确定父类链、ro、以及rw等,方法后续数据的读取以及查找的循环 - 是否
初始化
,如果没有,则initializeAndLeaveLocked
初始化
-
【第三步】
for循环
,按照类继承链 或者 元类继承链
的顺序查找-
当前cls的
方法列表
中使用二分查找算法
查找方法,如果找到,给imp赋值,并进入cache写入流程
(在cache 原理分析文章中已经详述过)log_and_fill_cache
-
当前cls
被赋值为父类
,如果父类等于nil
,则imp = forward_imp消息转发,并终止递归
,进入【第四步】 -
如果
父类链
中存在循环
,则报错,终止循环
// Halt if there is a cycle in the superclass chain. if (slowpath(--attempts == 0)) { _objc_fatal("Memory corruption in class list."); }
-
父类缓存
中查找方法- 如果
imp = forward_imp
消息转发,则跳出for循环 - 如果
imp找到,并且不是forward_imp
,则直接返回imp
,执行cache写入流程
- 如果
-
-
【第四步】for循环跳出后,如果未找到任何实现,则尝试一次方法解析
resolveMethod_locked
realizeClassMaybeSwiftAndLeaveLocked
realizeClassMaybeSwiftAndLeaveLocked
主要是为了确定父类链,为后续的for循环做数据的准备。
realizeClassMaybeSwiftAndLeaveLocked
内部调用
realizeClassMaybeSwiftMaybeRelock
,realizeClassMaybeSwiftMaybeRelock
调用realizeClassWithoutSwift
。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()) return cls;
ASSERT(cls == remapClass(cls));
// fixme verify class is not in an un-dlopened part of the shared cache?
auto ro = (const class_ro_t *)cls->data();
auto isMeta = ro->flags & RO_META;
if (ro->flags & RO_FUTURE) {
// This was a future class. rw data is already allocated.
rw = cls->data();
ro = cls->data()->ro();
ASSERT(!isMeta);
cls->changeInfo(RW_REALIZED|RW_REALIZING, RW_FUTURE);
} else {
// Normal class. Allocate writeable class data.
rw = objc::zalloc<class_rw_t>();
rw->set_ro(ro);
rw->flags = RW_REALIZED|RW_REALIZING|isMeta;
cls->setData(rw);
}
#if FAST_CACHE_META
if (isMeta) cls->cache.setBit(FAST_CACHE_META);
#endif
// Choose an index for this class.
// Sets cls->instancesRequireRawIsa if indexes no more indexes are available
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)" : "");
}
// Realize superclass and metaclass, if they aren't already.
// This needs to be done after RW_REALIZED is set above, for root classes.
// This needs to be done after class index is chosen, for root metaclasses.
// This assumes that none of those classes have Swift contents,
// or that Swift's initializers have already been called.
// fixme that assumption will be wrong if we add support
// for ObjC subclasses of Swift classes.
//---- 递归找supercls metacls 把类和元类的继承链确定下来 为了后面查找父类cache
supercls = realizeClassWithoutSwift(remapClass(cls->superclass), nil);
metacls = realizeClassWithoutSwift(remapClass(cls->ISA()), nil);
...
// Update superclass and metaclass in case of remapping
//---- 把supercls和metacls放进去
cls->superclass = supercls;
cls->initClassIsa(metacls);
// Reconcile instance variable offsets / layout.
// This may reallocate class_ro_t, updating our ro variable.
if (supercls && !isMeta) reconcileInstanceVariables(cls, supercls, ro);
// Set fastInstanceSize if it wasn't set already.
cls->setInstanceSize(ro->instanceSize);
// Copy some flags from ro to rw
if (ro->flags & RO_HAS_CXX_STRUCTORS) {
cls->setHasCxxDtor();
if (! (ro->flags & RO_HAS_CXX_DTOR_ONLY)) {
cls->setHasCxxCtor();
}
}
// Propagate the associated objects forbidden flag from ro or from
// the superclass.
if ((ro->flags & RO_FORBIDS_ASSOCIATED_OBJECTS) ||
(supercls && supercls->forbidsAssociatedObjects()))
{
rw->flags |= RW_FORBIDS_ASSOCIATED_OBJECTS;
}
// Connect this class to its superclass's subclass lists
if (supercls) {
addSubclass(supercls, cls);
} else {
addRootClass(cls);
}
// Attach categories
//---- 把方法列表、属性、protocol都贴到rwe(rw->ext())中去
methodizeClass(cls, previously);
return cls;
}
说明:
- 会对
data()
、ro()
赋值 - 递归找
supercls
、metacls
把类和元类的继承链确定下来 为了后面查找父类cache - 通过
cls->superclass = supercls;
和addSubclass(supercls, cls);
互相确认父子关系看出class是双向链表 methodizeClass
方法中把方法列表、属性、protocol
都贴到rwe(rw->ext())
中去
getMethodNoSuper_nolock 查看当前类curClass中是否有方法
流程如下所示:
static method_t *
getMethodNoSuper_nolock(Class cls, SEL 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;
}
核心会调用search_method_list_inline
,其二分查找核心的源码实现如下:
static method_t *
findMethodInSortedMethodList(SEL key, const method_list_t *list)
{
ASSERT(list);
const method_t * const first = &list->first;
const method_t *base = first;
const method_t *probe;
uintptr_t keyValue = (uintptr_t)key;
uint32_t count;
//sel在method_list中是递增的
for (count = list->count; count != 0; count >>= 1) {
probe = base + (count >> 1);
uintptr_t probeValue = (uintptr_t)probe->name;
if (keyValue == probeValue) {
// `probe` is a match.
// Rewind looking for the *first* occurrence of this value.
// This is required for correct category overrides.
//---- 当找到sel 并且分类里有同名方法 需要probe--
//方法先加载类 然后再加载分类方法
while (probe > first && keyValue == (uintptr_t)probe[-1].name) {
probe--;
}
return (method_t *)probe;
}
if (keyValue > probeValue) {
base = probe + 1;
count--;
}
}
return nil;
}
从第一次查找开始,每次都取中间位置
,与想查找的key的value值
作比较,如果相等
,则需要排除分类方法
,然后将查询到的位置的方法实现返回,如果不相等
,则需要继续二分查找
,如果循环至count = 0
还是没有找到
,则直接返回nil
,如下所示:
以查找LGPerson
类的say666实例方法
为例,其二分查找过程如下:
getMethodNoSuper_nolock
查找完毕,回到lookUpImpOrForwar
方法 如果meth
有值 ,则给imp
赋值并且缓存方法goto done
:
- goto done
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
cache_fill(cls, sel, imp, receiver);//把方法写入缓存
}
cache_fill
把方法写入缓存.为了下次调用此方法时使用快速查找,提高性能。
总结下流程: objc_msgSend查找缓存->缓存没有则慢速二分查找->如果找到则cache_fill缓存方法->下次还调用此方法则用objc_msgSend查找缓存快速流程。
如果慢速二分查找没找到,则会找superclass cache (**if** (slowpath((curClass = curClass->getSuperclass()) == **nil**))
(已经把superclass赋值给curClass了), 继续走lookUpImpOrForward
本次for循环后续流程。
cache_getImp方法:父类缓存查找
STATIC_ENTRY _cache_getImp
GetClassFromIsa_p16 p0
CacheLookup GETIMP, _cache_getImp
LGetImpMiss:
mov p0, #0
ret
END_ENTRY _cache_getImp
cache_getImp
方法是通过汇编_cache_getImp实现
,传入的$0
是 GETIMP
,如下所示:
-
如果
父类缓存
中找到了方法实现,则跳转至CacheHit
即命中,则直接返回imp
-
如果在
父类缓存
中,没有找到
方法实现,则跳转至CheckMiss
或者JumpMiss
,通过判断$0
跳转至LGetImpMiss
,直接返回nil
resolveMethod_locked 进入动态方法决议
跳出for循环,此时imp=forward_imp,没有找到。 继续往下会走到下面方法:
// No implementation found. Try method resolver once.
if (slowpath(behavior & LOOKUP_RESOLVER)) { //动态方法决议只会来一次
behavior ^= LOOKUP_RESOLVER;
return resolveMethod_locked(inst, sel, cls, behavior);//进入动态方法决议
}
resolveMethod_locked
进入动态方法决议
_objc_msgForward_impcache
在上面lookUpImpOrForward
源码中forward_imp
默认是_objc_msgForward_impcache
。
- 其中_objc_msgForward_impcache是汇编实现,会跳转至
__objc_msgForward
,其核心是__objc_forward_handler
:
STATIC_ENTRY __objc_msgForward_impcache
// No stret specialization.
b __objc_msgForward
END_ENTRY __objc_msgForward_impcache
//👇
ENTRY __objc_msgForward
adrp x17, __objc_forward_handler@PAGE
ldr p17, [x17, __objc_forward_handler@PAGEOFF]
TailCallFunctionPointer x17
END_ENTRY __objc_msgForward
汇编实现中查找__objc_forward_handler
,并没有找到,在源码中去掉一个下划线进行全局搜索_objc_forward_handler
,有如下实现,本质是调用c/c++的objc_defaultForwardHandler
方法:
// 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;
这就是我们在日常开发中最常见的错误:没有实现函数,运行程序,崩溃时报的错误提示
。
lookUpImpOrForward
流程总结
-
对于
对象方法(即实例方法)
,即在类中查找
,其慢速查找的父类链
是:类--父类--根类--nil
-
对于
类方法
,即在元类中查找
,其慢速查找的父类链
是:元类--根元类--根类--nil
-
如果
快速查找、慢速查找
也没有找到方法实现,则尝试动态方法决议