方法的慢速查找流程

1. __objc_msgSend_uncached

在上篇文章中提到当在类的缓存中没有对应的方法的实现时,会去执行__objc_msgSend_uncached。将会开启方法慢速查找流程

__objc_msgSend_uncached

	STATIC_ENTRY __objc_msgSend_uncached
	UNWIND __objc_msgSend_uncached, FrameWithNoSaves

	// THIS IS NOT A CALLABLE C FUNCTION
	// Out-of-band p15 is the class to search
	
	MethodTableLookup
	
	/*
 .macro TailCallFunctionPointer
     // $0 = function pointer value
     braaz    $0
 .endmacro
 */
	TailCallFunctionPointer x17

	END_ENTRY __objc_msgSend_uncached
复制代码

MethodTableLookup

.macro MethodTableLookup
	//保存方法参数到寄存器中
	SAVE_REGS MSGSEND

	// lookUpImpOrForward(obj, sel, cls, LOOKUP_INITIALIZE | LOOKUP_RESOLVER)
	// receiver and selector already in x0 and x1
    // 位移 为调用_lookUpImpOrForward方法准备参数
    // receiver 和 selector此时已经存储到x0 和 x1 了

    //将class作为第三个参数
	mov	x2, x16
    //第四个参数传递 3
	mov	x3, #3
    
    //bl bl
    // 如果缓存中未找到,则跳转到 _lookUpImpOrForward(c 函数) 去方法列表中去找函数,
	bl	_lookUpImpOrForward

	// IMP in x0
	mov	x17, x0

    // 恢复寄存器并返回
	RESTORE_REGS MSGSEND

.endmacro
复制代码

__objc_msgSend_uncached就两部分:执行MethodTableLookupTailCallFunctionPointerTailCallFunctionPointer其实是一个跳转指令宏定义,跳转到方法实现的地址去执行,简言之就是器执行方法。所以方法的查找主要看MethodTableLookup就行了。

MethodTableLookup主要执行流程:

  • 保存参数到寄存器中,为_lookUpImpOrForward执行做准备
    • x0 消息接收者(self)
    • x1 selector
    • x2 class
    • x3 3
  • 跳转到lookUpImpOrForward函数执行,去进行慢速查找
  • lookUpImpOrForward方法的返回值,存储在x0, 将x0寄存器的值存储到x17, 后面会跳转到x17地址去执行

2.lookUpImpOrForward 慢速方法查找

lookUpImpOrForward的主要作用就是从cls的方法列表里查找imp,如果没有找到,就去父类中查找(包括父类的缓存和方法列表)。

  • 如果找到了imp,就存储到classcache_t中,以便下次调用时可以快速查找,并将imp返回
  • 如果没有找到imp,就进行行为(behavior)判断:
    • 如果behavior允许进行方法决议,就去执行方法决议resolveMethod_locked),并将决议结果 return
    • 如果behavior不再允许方法决议,继续向下执行
  • done:标签处的代码,会将imp记录,并缓存到cache_t
    • 此时imp可能是真正的方法实现
    • 也可能是由于没有找到真正的方法实现,而被赋值的替代forward_imp
  • 执行结果返回
    • 可能返回真正的方法实现地址
    • 可能返回nil
    • 可能返回forward_imp
IMP lookUpImpOrForward(id inst, SEL sel, Class cls, int behavior)
{
    //1. forward_imp  当慢速查找未找到imp时返回
    //实际是 objc_defaultForwardHandler
    const IMP forward_imp = (IMP)_objc_msgForward_impcache;
    //临时变量
    IMP imp = nil;
    Class curClass;

    runtimeLock.assertUnlocked();

    //2. 查找方法列表前的判断和准备
    //2.1 判断类是否初始化,如果没有, 就标记behavior 为不缓存imp
    if (slowpath(!cls->isInitialized())) {
        // |= 与运算 并将结果进行赋值
        behavior |= LOOKUP_NOCACHE; 
    }

    //2.2 runtimeLock 在方法查找和缓存期间保持, 以使方法方法添加保持原子性.
    //加锁
    runtimeLock.lock();

    //2.3 检查是否是已知类 不是就报错 中断执行
    checkIsKnownClass(cls);

    //2.4 检查类是否已实现, 没有就实现一下
    //检查类是否已初始化, 没有就进行初始化
    cls = realizeAndInitializeIfNeeded_locked(inst, cls, behavior & LOOKUP_INITIALIZE);
    
    // 确保已经加锁
    runtimeLock.assertLocked();
    
    //为curClass赋初值, 初始值为cls
    curClass = cls;

    // The code used to lookup 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().
    // 唯一没有执行某种缓存查找的代码路径就是class_getInstanceMethod()。
    
    //for 循环负责遍历查找 根据sel找到imp
    //unreasonableClassCount() 获取类的迭代的上限
    for (unsigned attempts = unreasonableClassCount();;) {
        if (curClass->cache.isConstantOptimizedCache(/* strict */true)) {
#if CONFIG_USE_PREOPT_CACHES
            imp = cache_getImp(curClass, sel);
            if (imp) goto done_unlock;
            curClass = curClass->cache.preoptFallbackClass();
#endif
        } else {
            // curClass method list.
            Method meth = getMethodNoSuper_nolock(curClass, sel);
            if (meth) {
                imp = meth->imp(false);
                goto done;
            }

            if (slowpath((curClass = curClass->getSuperclass()) == 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)) {
        //LOOKUP_RESOLVER = 2  behavior = 3
        //behavior 异或为1
        behavior ^= LOOKUP_RESOLVER;
        return resolveMethod_locked(inst, sel, cls, behavior);
    }

 done:
    if (fastpath((behavior & LOOKUP_NOCACHE) == 0)) {
#if CONFIG_USE_PREOPT_CACHES
        while (cls->cache.isConstantOptimizedCache(/* strict */true)) {
            cls = cls->cache.preoptFallbackClass();
        }
#endif
        log_and_fill_cache(cls, imp, sel, inst, curClass);
    }
 done_unlock:
    //解锁
    runtimeLock.unlock();
    
    //返回imp
    if (slowpath((behavior & LOOKUP_NIL) && imp == forward_imp)) {
        return nil;
    }
    return imp;
}
复制代码

forward_imp实际就是objc_defaultForwardHandler,一个c方法。当没有找到真正的imp时,会返回objc_defaultForwardHandler的地址,然后调用。这时候就是我们熟悉的报错了,“xxx unrecognized selector sent to instance xxx”。

__attribute__((noreturn, cold)) void
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
复制代码

遍历查找的过程大致如下:

  1. curClass的方法列表里查找, 列表路径: cls->data()->methods()
    1.1 如果找到imp, 就跳到done标记处 继续向下执行
    1.2 如果没有找到, curClass指向curClass父类, 准备去父类中查找
    1.3 如果curClass父类 为nil, 说明遍历完所有的父类都没有找到imp, 循环结束, break

  2. curClass(父类)cache中查找, 缓存中是否有imp
    2.1 如果imp不为空, 但是imp=forward_imp, 说明此前父类在查找sel对应的imp时没有找到,只能将forward_imp放入缓存,这时也不用继续找了,因为找也找不到. 结束循环, break
    2.2 如果imp不为空, 并且不等于forward_imp, 说明在父类中找到了对应的imp, 跳到done标记处 继续向下执行
    2.3 imp为空, curClass(父类)的缓存中没有没有找到, 继续循环, 去curClass(父类)的方法列表里查找, 此时开始下次一次遍历查找。

  3. 如果第二次遍历还没找到就去curClass(父类)的父类(父类的父类)的缓存中查找, 如此循环往复, 直到结束(找到或未找到)。找到跳转到done标记,没找到会向下进行方法决议判断。

方法决议的判断如下:

if (slowpath(behavior & LOOKUP_RESOLVER)) {
        //LOOKUP_RESOLVER = 2  behavior = 3
        //behavior 异或为1
        behavior ^= LOOKUP_RESOLVER;
        return resolveMethod_locked(inst, sel, cls, behavior);
    }
复制代码

当没有找到方法实现的时候,会尝试一次方法决议。
之所以说是一次, 是因为从MethodTableLookup汇编代码中,调用lookUpImpOrForward方法时,behavior=3
进到if 里后, 会对behavior执行异或操作,之后behavior=1
resolveMethod_locked方法决议时,会再次尝试调用lookUpImpOrForward, 这时behavior=1, 不能再去if里去执行了。
将会向下执行done:

done:标记的执行,也会有两种情况:

  1. 通过上面的for循环,找到了方法的实现,直接跳转到done执行
  2. 通过上面的for循环,没有找到方法的实现,进行方法决议, 在方法决议时会再次执行lookUpImpOrForward方法,仍然没有找到方法实现imp. 前面说过,方法决议只会执行一次, 所以不会再去方法决议了, 而是向下执行done:

执行可能2时, imp=forward_imp, 会把forward_imp存入cls的缓存中。

lookUpImpOrForward方法的慢速查找流程,基本了解了。再去resolveMethod_locked看一下方法决议的过程。这也是慢速查找流程的重要部分。

3. 方法决议

在进入resolveMethod_locked查看方法执行过程前,先提前了解几个重要方法的实现和作用。

3.1 resolveInstanceMethod:和 resolveClassMethod:

NSObject.h
+ (BOOL)resolveClassMethod:(SEL)sel OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);
+ (BOOL)resolveInstanceMethod:(SEL)sel OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);

NSObject.mm
+ (BOOL)resolveClassMethod:(SEL)sel {
    return NO;
}
+ (BOOL)resolveInstanceMethod:(SEL)sel {
    return NO;
}
复制代码

resolveClassMethod: 为类方法的给定选择器提供动态实现。
resolveInstanceMethod: 为实例方法的给定选择器提供动态实现。
这两个类方法在基类NSObject中声明和实现,目的是当在慢速查找imp时,如果没有找到imp,可以当前的sel动态的添加方法实现。默认返回NO
子类可以重写这个方法,为指定的sel添加动态实现,然后返回YES,表示进行了这个方法的动态决议。

以官方提供的例子为例:
Objective-C 方法只是一个 C 函数,它至少接受两个参数——self和_cmd。使用该函数,可以将函数作为方法添加到类中。

void dynamicMethodIMP(id self, SEL _cmd)
{
    // implementation ....
}
复制代码

给定以下函数:class_addMethod,可以使用它动态地将dynamicMethodIMP作为方法(称为)添加到类中,如下所示:resolveInstanceMethod:resolveThisMethodDynamically

+ (BOOL) resolveInstanceMethod:(SEL)aSEL
{
    if (aSEL == @selector(resolveThisMethodDynamically))
    {
          class_addMethod([self class], aSEL, (IMP) dynamicMethodIMP, "v@:");
          return YES;
    }
    return [super resolveInstanceMethod:aSel];
}
复制代码

3.2 resolveInstanceMethod/resolveClassMethod

static void resolveInstanceMethod(id inst, SEL sel, Class cls)
{
    runtimeLock.assertUnlocked();
    ASSERT(cls->isRealized());
    //注册resolveInstanceMethod:方法签名 为调用方法准备
    //resolveInstanceMethod: 是一个类方法, 可在自定义实现, 进行方法的决议
    //
    SEL resolve_sel = @selector(resolveInstanceMethod:);

    //判断resolveInstanceMethod是否已实现 没有就直接return
    if (!lookUpImpOrNilTryCache(cls, resolve_sel, cls->ISA(/*authenticated*/true))) {
        // Resolver not implemented.
        return;
    }

    //调用resolveInstanceMethod 方法
    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
    // 查找sel的imp, imp可能为空
    // lookUpImpOrNilTryCache执行过程中会调用lookUpImpOrForward,
    // 如果经过方法决议后, 有了sel的方法实现imp, 经过查找, 会将imp 存入缓存, 以后方法决议就不会再触发了
    IMP imp = lookUpImpOrNilTryCache(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:方法进行方法决议,然后进行一次imp查找。
lookUpImpOrNilTryCache可能会返回空值 nil。这里执行结果imp, 并不会返回,只是输出记录的时候使用一下。但是找到的imp(不管是真正的方法实现,还是forward_imp)会被缓存起来,后续在查找时,直接在缓存中就可以找到了。
有时候resolveInstanceMethod:可能返回YES,但是并没有真正的添加方法实现。所以经过一次查找之后,可以最终确认一下。然后将决议结果记录输出。

resolveClassMethod参照resolveInstanceMethod即可。

3.3 lookUpImpOrNilTryCache/lookUpImpOrForwardTryCache

lookUpImpOrNilTryCache, 查找imp, 找不到会返回nil。behavior | LOOKUP_NIL使得behavior & LOOKUP_NIL结果为真。具体判断参见lookUpImpOrForward的结尾部分。

IMP lookUpImpOrNilTryCache(id inst, SEL sel, Class cls, int behavior)
{
    //behavior | LOOKUP_NIL = 7
    return _lookUpImpTryCache(inst, sel, cls, behavior | LOOKUP_NIL);
}
复制代码

lookUpImpOrForwardTryCache,查找imp, 找不回会返回一个默认的方法实现 _objc_msgForward_impcache

IMP lookUpImpOrForwardTryCache(id inst, SEL sel, Class cls, int behavior)
{
    return _lookUpImpTryCache(inst, sel, cls, behavior);
}
复制代码

3.4 _lookUpImpTryCache

static IMP _lookUpImpTryCache(id inst, SEL sel, Class cls, int behavior)
{
    // behavior = 1
    runtimeLock.assertUnlocked();
   
    if (slowpath(!cls->isInitialized())) {
        // see comment in lookUpImpOrForward
        // cls未初始化, 去lookUpImpOrForward查找sel的imp,
        // 此时behavior = 1 不会再进行方法决议
        return lookUpImpOrForward(inst, sel, cls, behavior);
    }

    //从缓存中查找 快速查找
    IMP imp = cache_getImp(cls, sel);
    //imp 不为空 直接返回
    if (imp != NULL) goto done;
#if CONFIG_USE_PREOPT_CACHES
    if (fastpath(cls->cache.isConstantOptimizedCache(/* strict */true))) {
        imp = cache_getImp(cls->cache.preoptFallbackClass(), sel);
    }
#endif
    
    //imp为空, 进行慢速查找 并将结果返回
    if (slowpath(imp == NULL)) {
        return lookUpImpOrForward(inst, sel, cls, behavior);
    }

    //返回imp 或 nil
done:
    if ((behavior & LOOKUP_NIL) && imp == (IMP)_objc_msgForward_impcache) {
        return nil;
    }
    return imp;
}
复制代码

_lookUpImpTryCache是一个标准的imp查找方法。先去查找缓存cache_getImp,找不到就去进行慢速查找lookUpImpOrForward

注意,如果是在方法慢速查找过程中执行_lookUpImpTryCache,说明第一遍的慢速查找没有找到,进行了方法决议,方法决议后再次查找。如果执行到lookUpImpOrForward,这已经是第二次执行它了,behavior已经与LOOKUP_RESOLVER(值为2)执行过异或了, 与LOOKUP_RESOLVER(2)做与运算不会再为真了,所以不可能在进行一次方法决议了。

3.5 resolveMethod_locked

经过前面几个方法介绍,再来看resolveMethod_locked方法决议流程就清楚多了。

static NEVER_INLINE IMP
resolveMethod_locked(id inst, SEL sel, Class cls, int behavior)
{
    //behavior=1
    
    runtimeLock.assertLocked();
    ASSERT(cls->isRealized());

    runtimeLock.unlock();
   
    if (! cls->isMetaClass()) {
        //cls不是元类 inst为实例对象 sel为实例方法
        //进行实例方法决议
        // try [cls resolveInstanceMethod:sel]
        resolveInstanceMethod(inst, sel, cls);
    }
    else {
        //cls是元类 inst是类对象  sel是类方法
        
        
        // try [nonMetaClass resolveClassMethod:sel]
        // and [cls resolveInstanceMethod:sel]
        
        //先进行类方法决议
        resolveClassMethod(inst, sel, cls);
        
        //调用lookUpImpOrNilTryCache 判断经过类方法决议之后, sel是否已经有了对应的imp
        if (!lookUpImpOrNilTryCache(inst, sel, cls)) {
            //如果仍然没有找到imp, 再进行一次实例方法决议
            //为什么可以再进行一次实例方法决议, 通过isa走位图, 根元类的父类是 NSObject类, NSObject类里存储的实例方法, 所以可以再进行一次实例方法决议.
            resolveInstanceMethod(inst, sel, cls);
        }
    }

    // chances are that calling the resolver have populated the cache
    // so attempt using it
    // 进行方法决议之后, 可能为sel添加了方法实现imp, 因此再进行一次方法查找
    // 此时的behavior=1 即使还找到imp也不会再进行方法决议了, 而是返回imp(可能不是真正的imp, 而是_objc_msgForward_impcache)
    // 将结果返回
    return lookUpImpOrForwardTryCache(inst, sel, cls, behavior);
}
复制代码

resolveMethod_locked会对当前的cls进行判断,执行不同的方法决议流程。
如果cls不是元类,那么当前inst为实例对象 sel为实例方法,执行实例方法决议流程。
如果cls是元类,那么当前inst是类对象 sel是类方法,执行类方法决议流程:

  • 先进行类方法决议resolveClassMethod
  • 然后查找imp, 判断经过类方法决议之后, sel是否已经有了对应的imp
  • 如果仍然没有找到imp, 再进行一次实例方法决议

为什么可以再进行一次实例方法决议?
通过isa走位图可以, 根元类的父类是 NSObject类。 NSObject类里存储的实例方法, 加入在NSObject的实例决议方法,所以可以再进行一次实例方法决议.

最后,会调用lookUpImpOrForwardTryCache,进行imp的查找并将结果返回(可能是真正的imp, 也可能是_objc_msgForward_impcache)。

我们已经知道在前面执行

resolveInstanceMethod(inst, sel, cls);
复制代码

或者

resolveClassMethod(inst, sel, cls);
复制代码

时,方法内部已经进行了一次imp查找。这次执行lookUpImpOrForwardTryCache在缓存中就会找到imp了。

方法的慢速查到流程算是补充完整了。
下面是一个小例子。

4. ?

定义ZPerson类,声明sayHellosayNB方法,但是只实现了sayNB方法。

@interface ZPerson : NSObject
- (void)sayHello;
- (void)sayNB;
@end

@implementation ZPerson
//- (void)sayHello{
//    NSLog(@"你好");
//}

- (void)sayNB{
    NSLog(@"你牛逼");
}
@end
复制代码

创建ZPerson实例,并执行sayHello方法。由于没有提供sayHello的实现和+resolveInstanceMethod:的实现,不出意外会崩溃。

ZPerson *person = [ZPerson alloc];
[person sayHello];
复制代码

执行结果:
图片[1]-方法的慢速查找流程-一一网
不出所料,而且由于没有找到真正的方法实现,而执行了objc_defaultForwardHandler

下面实现+resolveInstanceMethod:方法:

+ (BOOL)resolveInstanceMethod:(SEL)sel{
    if (sel == @selector(sayHello)) {
        NSLog(@"执行 sayHello 方法决议");
        
        IMP sayHelloIMP = class_getMethodImplementation(self, @selector(sayNB));
    
        Method sayHelloMethod = class_getInstanceMethod(self, @selector(sayNB));
        
        const char *sayHelloType = method_getTypeEncoding(sayHelloMethod);
        
        return class_addMethod(self, sel, sayHelloIMP, sayHelloType);
    }
    return [super resolveInstanceMethod:sel];
}
复制代码

在决议方法中,动态为sayHello提供了实现,不会崩溃了。会转而执行sayNB方法。

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享