AI智能
改变未来

iOS OC 方法查找流程

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()

方法中

  1. 先根据传入的参数
    cache

    ,为

    ture

    时,再次通过

    cache_getImp(cls, sel)

    方法,用汇编去查找

    imp

    ,查找到直接返回

    imp

    ;为

    false

    时,直接跳过。

  2. 判断
    !cls->isRealized()

    ,调用

    realizeClass(cls)

    ,做准备工作(根据

    class

    中

    rw data()->flags & RW_REALIZED

    ),比如

    父类 元类 rw ro

    等。

if (!cls->isRealized()) {realizeClass(cls);}
  1. 准备工作完成后,再次尝试
    cache_getImp(cls, sel)

    ,查找到

    imp

    ,直接返回

    imp

    .

imp = cache_getImp(cls, sel);if (imp) goto done;
  1. 试图在

    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;}
  2. 当

    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;}}}
  1. 当
    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;}
  1. _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

,重新查找。

  1. 当
    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私信关注

赞(0) 打赏
未经允许不得转载:爱站程序员基地 » iOS OC 方法查找流程