iOS OC 方法查找流程
- 前言
- 1. `_class_lookupMethodAndLoadCache3`方法查找流程
- 2. 面试题
前言
上一篇关于
方法的本质
的探索中,我们知道了
方法
的底层是调用
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. clsIMP 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
,所以不会崩溃,并且会调用该方法。
- 点赞1
- 收藏
- 分享
- 文章举报
亮亮不想说话发布了18 篇原创文章 · 获赞 10 · 访问量 265私信关注