在上一篇文章中(oc-底层原理之objc_msgSend方法快速查找)我们知道objc_msgSend在发送消息时首先会从缓存中去查找方法的IMP,但是如果缓存没有命中时会怎么处理呢?这一篇文章我们将关注objc_msgSend的慢速查找过程
lookUpImpOrForward
我们通过objc_msgSend源码知道,在缓存查找方法失败的时候会调用CheckMiss或者JumpMiss,我们看看这两个方法的源码:
.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
因为传入的参数为NORMAL,所以两个方法最终都会调用__objc_msgSend_uncached,我们再来看看这部分源码:
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,继续源码:
.macro MethodTableLookup
//部分代码已省略
bl _lookUpImpOrForward
//部分代码已省略
.endmacro
lookUpImpOrForward是整个慢速查找的入口,接下来我们重点研究lookUpImpOrForward
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();
// Optimistic cache lookup
if (fastpath(behavior & LOOKUP_CACHE)) {
imp = cache_getImp(cls, sel);
if (imp) goto done_nolock;
}
// runtimeLock is held during isRealized and isInitialized checking
// to prevent races against concurrent realization.
// runtimeLock is held during method search to make
// method-lookup + cache-fill atomic with respect to method addition.
// 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();
// We don't want people to be able to craft a binary blob that looks like
// a class but really isn't one and do a CFI attack.
//
// To make these harder we want to make sure this is a class that was
// either built into the binary or legitimately registered through
// objc_duplicateClass, objc_initializeClassPair or objc_allocateClassPair.
//
// TODO: this check is quite costly during process startup.
checkIsKnownClass(cls);
if (slowpath(!cls->isRealized())) {
cls = realizeClassMaybeSwiftAndLeaveLocked(cls, runtimeLock);
// runtimeLock may have been dropped but is now locked again
}
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().
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)) {
// 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.
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;
}
}
// No implementation found. Try method resolver once.
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;
}
其他的代码暂时可以先不看,重点关注for循环中的代码,for循环的大致执行流程如下:
-
curClass指向当前类,首先从当前类中查找,如果从当前类的methodList找到,则填充缓存并返回IMP -
如果当前类中并没有找到,当前类的父类不为空(至于父类为什么会为空,请去查看一下继承链),则
curClass = curClass->superclass -
curClass指向父类以后,首先通过cache_getImp从父类的缓存中查找,如果缓存中找到则直接返回IMP,否则,循环查找 -
如果父类为nil,则
imp会被赋值为forward_imp,forward_imp到底是什么呢 ?我们可以看到const IMP forward_imp = (IMP)_objc_msgForward_impcache;最终我们在汇编代码中找到了
_objc_msgForward_impcache方法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的实现: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;
是不是很熟悉这段报错,我们都知道当方法未实现时,都会报错unrecognized selector sent to instance,这是因为当方法未找到时,都会返回一个IMP
如果没有找到对应的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的源码
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);
}
我们注意到最终还是会调用lookUpImpOrForward方法,不知你有没有疑问,如果IMP还是没有调用,是否会造成循环调用呢?
behavior异或
我们阅读源码发现,在调用resolveMethod_locked之前对behavior做了一个异或运算,这就保证了resolveMethod_locked在一次查找中只会调用一次,不会出现循环调用的情况
resolveMethod_locked
(源码请看上面,这里不再重复贴源码)
此处做了一个判断,当前的class是否是元类,最终会调用以下两个方法:
- resolveInstanceMethod
- resolveClassMethod
类方法在元类中以实例方法的形式存在,所以如果当前类是元类,则通过类方法的形式调用
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));
}
}
}
这段源码比较简单,实际上就是
- 判断类中
resolveInstanceMethod是否实现,如果未实现,则直接返回 - 如果实现了
resolveInstanceMethod方法,则通过objc_msgSend发送消息
我们可以通过设置
OBJC_PRINT_RESOLVED_METHODS环境变量来打印出resolveInstanceMethod的结果
我们通过一个demo来验证
首先定义一个WPerson类
@interface WPerson : NSObject
- (void)sayNB;
- (void)sayHello;
@end
@implementation WPerson
- (void)sayNB{
NSLog(@"%s",__func__);
}
@end
我们在类中只定义了两个方法sayNB和sayHello,但是我们在m文件中只实现了sayNB方法,我们再通过WPerson对象调用sayHello方法
WPerson *person = [WPerson alloc];
[person sayHello];
我们都清楚这样调用会造成程序崩溃,那么我们在WPerson类中实现resolveInstanceMethod方法
+ (BOOL)resolveInstanceMethod:(SEL)sel{
if (sel == @selector(sayHello)) {
NSLog(@"%@ 来了",NSStringFromSelector(sel));
IMP imp = class_getMethodImplementation(self, @selector(sayNB));
Method sayMMethod = class_getInstanceMethod(self, @selector(sayNB));
const char *type = method_getTypeEncoding(sayMMethod);
return class_addMethod(self, sel, imp, type);
}
return [super resolveInstanceMethod:sel];
}
打印结果:
[32756:432638] sayHello 来了
[32756]: RESOLVE: method -[WPerson sayHello] dynamically resolved to 0x100000c80
[32756:432638] -[WPerson sayNB]
在resolveInstanceMethod方法中我们动态的将sayHello的实现映射到了sayNB方法实现上,所以最终调用sayNB方法
resolveClassMethod
实现原理同resolveInstanceMethod,当我们向类方法发送消息的时候,如果类方法的IMP未找到,就会调用这个方法,具体源码可自行探究,这里不再赘述
总结
oc的方法查找分为
- 快速查找--从缓存中查找
- 慢速查找--如果缓存中未找到,则从
methodList查找(自己的methodList以及父类的methodList)- 动态方法决议:如果methodList中也没有找到对应的IMP,系统并不会马上抛出错误,而是通过查找
resolveInstanceMethod和resolveClassMethod来提供一个补偿的机会 - 消息转发:(下一章节讨论)
- 动态方法决议:如果methodList中也没有找到对应的IMP,系统并不会马上抛出错误,而是通过查找
通过以上的几种方式,共同组成了oc的方法查找流程