iOS OC 方法查找流程
前言
上一篇关于方法的本质的探索中,我们知道了方法的底层是调用objc_msgSend发送消息,并对objc_msgSend的底层汇编进行了分析。当用汇编快速查找,未查找到方法缓存时,会调用 MethodTableLookup,然后调用_class_lookupMethodAndLoadCache3,从汇编转到C,开启一系列的慢速查找,接下来我们对_class_lookupMethodAndLoadCache3的方法查找流程进行分析。
1. _class_lookupMethodAndLoadCache3方法查找流程
假如当调用LGStudent 调用对象方法sayHello时,底层通过objc_msgSend发送消息,通过汇编在LGStudent的 cache中快速查找sayHello的缓存,未找到时,会来的_class_lookupMethodAndLoadCache3,方法如下:
IMP _class_lookupMethodAndLoadCache3(id obj, SEL sel, Class cls)
{
return lookUpImpOrForward(cls, sel, obj,
YES/*initialize*/, NO/*cache*/, YES/*resolver*/);
// NO/*cache*/ 没有方法缓存
}
在IMP lookUpImpOrForward()方法中
- 先根据传入的参数
cache,为ture时,再次通过cache_getImp(cls, sel)方法,用汇编去查找imp,查找到直接返回imp;为false时,直接跳过。 - 判断
!cls->isRealized(),调用realizeClass(cls),做准备工作(根据class中rw data()->flags & RW_REALIZED),比如父类 元类 rw ro等。
if (!cls->isRealized()) {
realizeClass(cls);
}
- 准备工作完成后,再次尝试
cache_getImp(cls, sel),查找到imp,直接返回imp.
imp = cache_getImp(cls, sel);
if (imp) goto done;
-
试图在
class's method lists中查找方法,通过getMethodNoSuper_nolock获取meth
4.1.getMethodNoSuper_nolock中:循环取出
mlists后,调用method_t *m = search_method_list(*mlists, sel),通过sel去匹配,匹配到直接返回。4.2.
方法找到后,调用log_and_fill_cache,然后调用cache_fill (cls, sel, imp, receiver),调用cache_fill_nolock(cls, sel, imp, receiver),进入方法缓存流程,判断是否有缓存,是否超出容量的3/4,是否需要扩容,然后找到bucket, 偏移_occupied,然后set(key, imp),将方法缓存到Class的cache_t cache中,方便下次调用时,快速查找。Method meth = getMethodNoSuper_nolock(cls, sel); if (meth) { log_and_fill_cache(cls, meth->imp, sel, inst, cls); imp = meth->imp; goto done; } -
当
class's method lists中未找到方法时,即:sayHello方法在LGStudent中未定义,那么会试图在父类的缓存和方法列表中superclass caches and method lists查找。在查找
superclass过程中,按照父类->元类->根元类->根类(NSObject)的顺序,依次循环查找,先通过汇编cache_getImp查找superclass的cache,缓存命中,调用log_and_fill_cache,进入方法缓存流程直接返回imp; 在缓存中未找到时,查找Superclass method list,流程同 4.1、4.2 步骤,源码如下:
// Try superclass caches and method lists.
{
unsigned attempts = unreasonableClassCount();
for (Class curClass = cls->superclass;
curClass != nil;
curClass = curClass->superclass)
{
// Halt if there is a cycle in the superclass chain.
if (--attempts == 0) {
_objc_fatal("Memory corruption in class list.");
}
// Superclass cache.
imp = cache_getImp(curClass, sel);
if (imp) {
if (imp != (IMP)_objc_msgForward_impcache) {
// 进入方法缓存流程
log_and_fill_cache(cls, imp, sel, inst, curClass);
goto done;
}
else {
break;
}
}
// Superclass method list.
Method meth = getMethodNoSuper_nolock(curClass, sel);
if (meth) {
// 进入方法缓存流程
log_and_fill_cache(cls, meth->imp, sel, inst, curClass);
imp = meth->imp;
goto done;
}
}
}
- 当
class's method lists和superclass caches and method lists中都没有找到调用的方法时,即:调用的方法在类 父类 元类中都未实现,那么调用_class_resolveMethod(cls, sel, inst)方法,进行方法动态解析。
// 源码
if (resolver && !triedResolver) {
runtimeLock.unlock();
_class_resolveMethod(cls, sel, inst);
runtimeLock.lock();
// Don't cache the result; we don't hold the lock so it may have
// changed already. Re-do the search from scratch instead.
triedResolver = YES;
goto retry;
}
_class_resolveMethod方法实现
void _class_resolveMethod(Class cls, SEL sel, id inst)
{
if (! cls->isMetaClass()) { // 判断是否是元类
// try [cls resolveInstanceMethod:sel]
// 类 此时类中已经没有方法 直接执行 _class_resolveInstanceMethod
// 执行 + (BOOL)resolveInstanceMethod:(SEL)sel方法
_class_resolveInstanceMethod(cls, sel, inst);
}
else {
// try [nonMetaClass resolveClassMethod:sel]
// and [cls resolveInstanceMethod:sel]
_class_resolveClassMethod(cls, sel, inst);
if (!lookUpImpOrNil(cls, sel, inst,
NO/*initialize*/, YES/*cache*/, NO/*resolver*/))
{
_class_resolveInstanceMethod(cls, sel, inst);
}
}
}
_class_resolveInstanceMethod方法实现:
static void _class_resolveInstanceMethod(Class cls, SEL sel, id inst)
{
// 查找_resolveInstanceMethod方法,
if (! lookUpImpOrNil(cls->ISA(), SEL_resolveInstanceMethod, cls,
NO/*initialize*/, YES/*cache*/, NO/*resolver*/))
{
// Resolver not implemented.
return;
}
BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
// 是否对未实现的方法动态解析
bool resolved = msg(cls, SEL_resolveInstanceMethod, 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(cls, sel, inst,
NO/*initialize*/, YES/*cache*/, NO/*resolver*/);
}
在_class_resolveInstanceMethod方法中,
先通过上面的方法查找流程查找resolveInstanceMethod方法的IMP(该方法是系统NSObject默认实现,+ (BOOL)resolveInstanceMethod:(SEL)sel,默认返回 NO),查找到IMP后,向cls发送resolveInstanceMethod消息,参数是sel(为实现的方法)。
所以,我们可以在重新系统的 resolveInstanceMethod 方法,在此方法中对为实现的方法进行动态解析,
防止因为调用未实现的方法引起的系统崩溃。
假如,我们在resolveInstanceMethod方法中,对方法进行了动态解析,那么这个方法的IMP,会加入到对应的cache中,然后跳转到 步骤6,然后retry,重新查找。
- 当
retry之后,依然没有查找到IMP,调用下面汇编。__objc_msgForward_impcache的汇编代码:
然后调用__objc_forward_handler,从汇编调用OC方法,如下:
到此,报出经典错误。
补充:重新resolveInstanceMethod示例:
+ (BOOL)resolveInstanceMethod:(SEL)sel{
// saySomething为为实现方法,当调用次方法时,就调用已经实现的sayHello方法
if (sel == @selector(saySomething)) {
IMP sayHIMP = class_getMethodImplementation(self, @selector(sayHello));
Method sayHMethod = class_getInstanceMethod(self, @selector(sayHello));
const char *sayHType = method_getTypeEncoding(sayHMethod);
return class_addMethod(self, sel, sayHIMP, sayHType);
}
NSLog(@"来了 老弟 - %p",sel);
return [super resolveInstanceMethod:sel];
}
2. 面试题
定义下面代码: LGStudent 调用 对象方法 sayMaster,是否会崩溃?为什么?
@interface LGPerson : NSObject
- (void)sayNB;
+ (void)sayHappay;
@end
#import "LGPerson.h"
@implementation LGPerson
- (void)sayNB{
NSLog(@"%s",__func__);
}
+ (void)sayHappay{
NSLog(@"%s",__func__);
}
@end
//
#import "LGPerson.h"
@interface LGStudent : LGPerson
- (void)sayHello;
+ (void)sayObjc;
@end
#import "LGStudent.h"
@implementation LGStudent
- (void)sayHello{
NSLog(@"%s",__func__);
}
+ (void)sayObjc{
NSLog(@"%s",__func__);
}
@end
@interface NSObject (LG)
- (void)sayMaster;
+ (void)sayEasy;
@end
@implementation NSObject (LG)
- (void)sayMaster{
NSLog(@"%s",__func__);
}
+ (void)sayEasy{
NSLog(@"%s",__func__);
}
[LGStudent sayMaster];
@end
答:不会崩溃,LGStudent 继承自 LGPerson,LGStudent 调用 sayMaster 方法,因为本身没有 sayMaster 方法,会去父类LGPerson 中寻找,LGPerson 同样没有 sayMaster方法,接下来寻找 LGPerson 的元类,直到寻到 根元类,而 根元类 也没有 sayMaster 方法,最后寻找 根元类 的 父类NSObject, 父类NSObject中有对象方法 sayMaster,所以不会崩溃,并且会调用该方法。
来源:oschina
链接:https://my.oschina.net/u/4413473/blog/3234441
