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()方法中

  1. 先根据传入的参数cache,为ture时,再次通过cache_getImp(cls, sel)方法,用汇编去查找imp,查找到直接返回imp;为false时,直接跳过。
  2. 判断!cls->isRealized(),调用realizeClass(cls),做准备工作(根据classrw 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),将方法缓存到Classcache_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查找superclasscache,缓存命中,调用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 listssuperclass 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. 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,重新查找。

  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 继承自 LGPersonLGStudent 调用 sayMaster 方法,因为本身没有 sayMaster 方法,会去父类LGPerson 中寻找,LGPerson 同样没有 sayMaster方法,接下来寻找 LGPerson 的元类,直到寻到 根元类,而 根元类 也没有 sayMaster 方法,最后寻找 根元类 的 父类NSObject, 父类NSObject中有对象方法 sayMaster,所以不会崩溃,并且会调用该方法。

发布了17 篇原创文章 · 获赞 9 · 访问量 170

猜你喜欢

转载自blog.csdn.net/LiangliangSpeak/article/details/105458273