在之前写过一篇关于objc_msgSend
的流程分析,而它就是方法的快速查找;那么本文将介绍一下方法的慢速查找流程。
在objc_msgSend
的汇编代码中,当查找isa
完毕之后,他会跳转到CacheLookup
的汇编代码中执行,当系统在缓存中没有找到相应的方法,他就会继续跳转到CheckMiss
的汇编代码中。我们来看一下CheckMiss
的源码:
.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
当系统走到这个过程中,会继续执行NORMAL
中的cbz p9, __objc_msgSend_uncached
这一行代码,也就是说,我们就可以以这个方法为入口去探索慢速查找的过程。
我们进行全局搜索__objc_msgSend_uncached
这个,找到了MethodTableLookup
,这个是一个方法列表,继续搜索这个方法,我们就只能查找到一个跳转的bl _lookUpImpOrForward
,之后我们就无法从找到这个方法的内容。
那么按照我们往常的套路,我们把下划线去掉,经过搜索lookUpImpOrForward
这个方法,找到了很多。
那么我们有没有别的途径去找到这个方法呢?
我们创建一个工程,在工程中创建一个类,在类中创建一个方法,在调用方法前打上断点:
把这个模式打开,可可以看到汇编中我们打上断点的方法处:
在方法下面的objc_msgSend
处打上断点:
就进入了objc_msgSend
方法中去了:
继续往下滑,就会看到下面图的样子,在_objc_msgSend_uncached
处打上断点,继续按住control+
接着就会跳转到_objc_msgSend_uncached
方法中去,就能看到lookUpImpOrForward at objc-runtime-new.mm:6099
这个信息。
下面继续描述一下方法的一些基本常识:
在一个类中,实例方法
存储在类
中,类方法
存储在元类
中;
那么当我们用调用类方法
的方式去调用实例方法
,它也会执行成功;那为什么用类方法的方式去调用实例方法,它也会成功呢?
答案在一张图中:
在isa
的走位中,根元类
最后也会走到NSObject
,因此,我们可以用调用类方法的方式去调用实例方法。
下面继续探索慢速查找流程:
由于在上面我们找到了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();
if (fastpath(behavior & LOOKUP_CACHE)) {
imp = cache_getImp(cls, sel);
if (imp) goto done_nolock;
}
runtimeLock.lock();
checkIsKnownClass(cls);
if (slowpath(!cls->isRealized())) {
cls = realizeClassMaybeSwiftAndLeaveLocked(cls, runtimeLock);
}
if (slowpath((behavior & LOOKUP_INITIALIZE) && !cls->isInitialized())) {
cls = initializeAndLeaveLocked(cls, inst, runtimeLock);
}
runtimeLock.assertLocked();
curClass = cls;
for (unsigned attempts = unreasonableClassCount();;) {
// curClass method list.
Method meth = getMethodNoSuper_nolock(curClass, sel);
if (meth) {
imp = meth->imp;
goto done;
}
if (slowpath((curClass = curClass->superclass) == nil)) {
imp = forward_imp;
break;
}
if (slowpath(--attempts == 0)) {
_objc_fatal("Memory corruption in class list.");
}
// Superclass cache.
imp = cache_getImp(curClass, sel);
if (slowpath(imp == forward_imp)) {
break;
}
if (fastpath(imp)) {
goto done;
}
}
if (slowpath(behavior & LOOKUP_RESOLVER)) {
behavior ^= LOOKUP_RESOLVER;
return resolveMethod_locked(inst, sel, cls, behavior);
}
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;
}
在main函数中调用一个方法,在方法调用处打上断点,在执行程序之后,在if (fastpath(behavior & LOOKUP_CACHE)) {
处打上断点,我们点击下一步,程序就会跳转到这一步上去;
我们发现,快速查找会查找缓存
中的方法,而慢速查找,它还要查找cache_getImp
,那这是为什么呢?
答案是因为要避免多线程
的影响,当你程序在调用时,线程可能偷偷摸摸的缓存了一次。
我们继续往下看源码,在下面有两个if slowpath
判断,我们从上面的if (slowpath(!cls->isRealized()))
的判断内去看它的实现:
cls = realizeClassMaybeSwiftAndLeaveLocked(cls, runtimeLock);
它里面存在一个方法realizeClassMaybeSwiftAndLeaveLocked
,点击方法进去,它返回的也是一个方法:realizeClassMaybeSwiftMaybeRelock
,继续点击,在realizeClassMaybeSwiftMaybeRelock
方法中,有一个关键的方法:realizeClassWithoutSwift
,点击进去,里面的代码很多,贴出一部分源码:
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();
rw->set_ro(ro);
rw->flags = RW_REALIZED|RW_REALIZING|isMeta;
cls->setData(rw);
}
在这个方法中,首先读cls
的data
数据,对rw,ro进行赋值,然后就有了数据;在有了数据之后,我们继续读下一块代码:
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)" : "");
}
supercls = realizeClassWithoutSwift(remapClass(cls->superclass), nil);
metacls = realizeClassWithoutSwift(remapClass(cls->ISA()), nil);
里面有一个supercls
和metacls
两个递归实现,利用realizeClassWithoutSwift
,把整个继承链递归下来,这符合那副经典的走位图。继续看下一块代码:
// Update superclass and metaclass in case of remapping
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
methodizeClass(cls, previously);
return cls;
在这一块中,有对cls->superclass
和cls->initClassIsa
进行赋值,然后在下面会对supercls
进行判断,分析这个if
判断,它是一个双向链表
结构,结下来的methodizeClass
是对所有列表和属性都会添加到rwe
当中去。
看完realizeClassMaybeSwiftAndLeaveLocked
的大部分内容之后,我们继续往下看另一个slowpath
中的initializeAndLeaveLocked
方法;继续点击方法进去,最终走到initializeAndMaybeRelock
方法,在这个方法中最重要的一行代码是调用的initializeNonMetaClass
这个方法,这个方法,贴出一块代码:
Class supercls;
bool reallyInitialize = NO;
initialize cls.
supercls = cls->superclass;
if (supercls && !supercls->isInitialized()) {
initializeNonMetaClass(supercls);
}
SmallVector<_objc_willInitializeClassCallback, 1> localWillInitializeFuncs;
{
monitor_locker_t lock(classInitLock);
if (!cls->isInitialized() && !cls->isInitializing()) {
cls->setInitializing();
reallyInitialize = YES;
localWillInitializeFuncs.initFrom(willInitializeFuncs);
}
}
if (reallyInitialize) {
_setThisThreadIsInitializingClass(cls);
if (MultithreadedForkChild) {
// LOL JK we don't really call +initialize methods after fork().
performForkChildInitialize(cls, supercls);
return;
}
for (auto callback : localWillInitializeFuncs)
callback.f(callback.context, cls);
if (PrintInitializing) {
_objc_inform("INITIALIZE: thread %p: calling +[%s initialize]",
objc_thread_self(), cls->nameForLogging());
}
上面的一块代码中,作用很明显,层层递归
初始化所有方法的类
;
知道这个作用之后,其他代码也就不那么重要了,回到lookUpImpOrForward
方法,继续往下看源码,它的返回值是imp
,既然是返回imp,如果它找不到imp怎么办呢?
那么我们就去看看imp赋值的地方在哪里,他的重点在for (unsigned attempts = unreasonableClassCount();;)
for循环中,代码在上面已经贴出来了。我们去研究这一段代码:
在代码中有一个方法getMethodNoSuper_nolock
获取cls
中的所有方法。
我们来看一下它的源码:
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)
{
// 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;
}
可以看到cls->data()->methods()
,这个data()
就是之前加载过来的ro,rw这些东西。
探索到这里,就涉及到一个算法,所有的方法都在method_list中,那么系统如何去查找方法呢?
答案:二分查找,二分查以中间点为界限,将精确的值确定在某个范围内,经过不断缩小范围,查找正确的内容;
那么是如何知道系统是通过二分查找去找到对应的方法呢?
在getMethodNoSuper_nolock
方法中有一个对method_t
类型的*m赋值的search_method_list_inline
函数,我们在这个函数中有一个findMethodInSortedMethodList
方法,下面附上这个方法的源码:
ALWAYS_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;
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.
while (probe > first && keyValue == (uintptr_t)probe[-1].name) {
probe--;
}
return (method_t *)probe;
}
if (keyValue > probeValue) {
base = probe + 1;
count--;
}
}
return nil;
}
在for循环遍历所有方法中,probe
的赋值中有一个count >> 1
位移操作,其作用是减少二分之一;
例如:8的二进制为1000; 8>>1 100 = 4;
通过上面的代码,我们可以了解到系统查找方法是使用二分查找
来寻找内容。
我们可以在控制台中打印list
中的内容,就可以打印该类中的所有方法。
那么在上面的代码中,probe--又是怎么理解呢?
它其实是来判断分类重名的作用;
我们来探索一下:
首先创建一个Person,里面创建一个方法,在对Person创建一个分类。分类中创建一个相同的方法,在main函数中调用,得到的结果是打印的是分类方法。这边就不贴代码了,实现起来比较简单。
当系统没有找到对应的方法之后,它就会执行goto done;
代码:
done:
log_and_fill_cache(cls, imp, sel, inst, curClass);
runtimeLock.unlock();
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);
}
那么在这个代码中,它会对方法进行缓存,以方便下次快速查找。
那么整个流程就是:objc_msgSend -> 二分查找自己 -> cache_fill ->objc_msgSend
形成一个闭环。
如果在自己中没有找到方法,那么它就会去父类去找,在父类中快速查找没找到,他就会执行cache_getImp
方法,在经过搜索这个方法,没有找到,那么可以肯定的是,它存在汇编代码中(只要与缓存相关的,一般都在汇编代码中):
STATIC_ENTRY _cache_getImp
GetClassFromIsa_p16 p0
CacheLookup GETIMP, _cache_getImp
那么它又会执行CacheLookup
方法,接着又会跑到lookUpImpOrForward
方法中查找。
注意事项:当在父类中快速查找中没有找到方法,那么它不会继续递归进行慢速查找,而是继续找父类的父类(NSObject)的缓存中进行快速查找,当在NSObject的缓存中没有找到,那么继续找NSObject的父类nil的缓存,进入一个死循环过程。
那为什么在父类中没有慢速查找过程呢?
由于imp的获取在cache_getImp
中,通过全局搜索,它存在汇编当中,附上代码:
STATIC_ENTRY _cache_getImp
GetClassFromIsa_p16 p0
CacheLookup GETIMP, _cache_getImp
可以看到它的CacheLookup
是GETIMP
类型;因此,在CacheLookup
的汇编代码最后会在JumpMiss
中,附上他的代码:
.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
可以看到,只有当CacheLookup
为NORMAL
时,才会进行慢速查找。
而LGetImpMiss
的代码中,它最后只是进行return
返回。
那么如果在父类中还是没有找到,那么在之前就会对(curClass = curClass->superclass) == nil)
会判断,将imp
赋值为forward_imp
,之后会执行这一句判断imp == forward_imp
,相等的话,就会递归出去,跳出循环。
那么这个forward_imp
又是什么东西呢?
在上面的代码中,forward_imp是通过_objc_msgForward_impcache
方法进行赋值的,我们去看看这个方法,经过全局搜索,它存在汇编中,代码我就不贴出来的,直接写出关键方法__objc_forward_handler
,搜索_objc_forward_handler
,就得到一个很有意思的东西:
__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);
}
上面的代码,相信很多iOS开发者都见过。当方法找不到时,就会显示这个错误。
那么在报错之前,它还会走到下面这部分代码中:
if (slowpath(behavior & LOOKUP_RESOLVER)) {
behavior ^= LOOKUP_RESOLVER;
return resolveMethod_locked(inst, sel, cls, behavior);
}
那么我们就去看看这部分代码中的内容,首先点击resolveMethod_locked
,他是一个动态方法决议;动态方法决议其实是系统在没有找到方法时,它给你一个处理过程,处理完之后,它再继续去查找,只要能找到方法所需要的内容,就不会报错,如果没有,则报出错误。
下面去看看它的源码:
static NEVER_INLINE IMP
resolveMethod_locked(id inst, SEL sel, Class cls, int behavior)
{
runtimeLock.assertLocked();
ASSERT(cls->isRealized());
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);
if (!lookUpImpOrNil(inst, sel, cls)) {
resolveInstanceMethod(inst, sel, cls);
}
}
// chances are that calling the resolver have populated the cache
// so attempt using it
return lookUpImpOrForward(inst, sel, cls, behavior | LOOKUP_CACHE);
}
在上面一段代码中,重要的就在那个if判断中,我们去看看resolveInstanceMethod
方法,请看源码:
static void resolveInstanceMethod(id inst, SEL sel, Class cls)
{
runtimeLock.assertUnlocked();
ASSERT(cls->isRealized());
SEL resolve_sel = @selector(resolveInstanceMethod:);
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));
}
}
}
这段源码作用就是只要能找到这个方法的实现,那么它就会去执行这个方法,动态方法协议其实是苹果给我们的一个机会,在快速和慢速都没找到相应的方法,那么它就给你一个去实现这个方法的机会,只要系统找到了这个方法,那么他就能执行成功。
下面我们用代码来试一下:
在声明文件中创建一个方法,在实现文件中不去写这个方法内容,那么我们在main函数中执行,它会报错,那么我们用另一种方式去写,就不会报错了:
+ (BOOL)resolveInstanceMethod:(SEL)sel{
if (sel == @selector(say666)) {
NSLog(@"%@ 来了",NSStringFromSelector(sel));
IMP imp = class_getMethodImplementation(self, @selector(sayMaster));
Method sayMMethod = class_getInstanceMethod(self, @selector(sayMaster));
const char *type = method_getTypeEncoding(sayMMethod);
return class_addMethod(self, sel, imp, type);
}
return [super resolveInstanceMethod:sel];
}
上面的代码是实例方法的动态决议,那么类方法的动态决议该如何实现呢?
类方法存在元类中,那么我们在添加对象时,需要将类对象替换成元类对象,而元类一层一层往上走到最后,它最后走到的也是NSObject中,因此,我们可以在实例方法中利用if
去进行判断,将类方法的动态决议也写在实例方法中,请看代码:
+ (BOOL)resolveInstanceMethod:(SEL)sel{
NSLog(@"%@ 来了",NSStringFromSelector(sel));
if (sel == @selector(say666)) {
NSLog(@"%@ 来了",NSStringFromSelector(sel));
IMP imp = class_getMethodImplementation(self, @selector(sayMaster));
Method sayMMethod = class_getInstanceMethod(self, @selector(sayMaster));
const char *type = method_getTypeEncoding(sayMMethod);
return class_addMethod(self, sel, imp, type);
}
else if (sel == @selector(sayNB)) {
IMP imp = class_getMethodImplementation(objc_getMetaClass("Person"), @selector(lgClassMethod));
Method sayMMethod = class_getInstanceMethod(objc_getMetaClass("Person"), @selector(lgClassMethod));
const char *type = method_getTypeEncoding(sayMMethod);
return class_addMethod(objc_getMetaClass("LGPerson"), sel, imp, type);
}
return NO;
}
在上面的代码中,say666
是实例方法,sayNB
是类方法,在代码中,区别很明显,类方法的对象是是要获得该类的元类。
上面的代码就是我们对中间层的处理,当系统没有在类中找到这个方法,那么就会启动动态决议,给我们一个处理的过程,处理完之后,再重新去执行lookUpImpOrForward
,这样就不会报错了。
重点:lookUpImpOrForward
这个方法会执行两次,第一次是动态方法解析,第二次是慢速转发过程。