objc_msgSend 流程之慢速查找

前言

在上篇文章中,我们结合源码分析了方法的快速查找(缓存查找),在最后,我们分析到如果缓存查找不到方法就会跳转至__objc_msgSend_uncached,进入方法的慢速查找,接下来我们就探究一下方法的慢速查找流程。(本次探究源码基于objc781).

准备工作

依旧是老样子我们定义一个ZGPerson类,代码如下:

@interface ZGPerson : NSObject{
    NSInteger age;
}
@property (nonatomic,copy) NSString *name;
@property (nonatomic,copy) NSString *nickName;
- (void)test1;
- (void)test2;
- (void)test3;
- (void)test4;
+ (void)testClass;
········分割线
@implementation ZGPerson
- (void)test1{
    NSLog(@"%s",__func__);
}
- (void)test2{
    NSLog(@"%s",__func__);
}
- (void)test3{
    NSLog(@"%s",__func__);
}
- (void)test4{
    NSLog(@"%s",__func__);
}
+ (void)testClass{
    NSLog(@"%s",__func__);
}
@end

main.m中调用一下test1方法

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // insert code here...
        
        ZGPerson *person = [ZGPerson alloc];
        [person test1];
    
    }
    return 0;
}

找寻慢速查找入口

我们在Xcode - Debug - Debug WorkFlow中将Always show Disamessbly打开,跟着断点调试

test1

接着我们按着control,点击红圈选择键进行单步调试发现

单步走

方法缓存查找不到之后会调用__objc_msgSend_uncached

__objc_msgSend_uncached

继续单步走

lookUpImpOrForward

发现走到了lookUpImpOrForward方法,在源码objc-runtime-new.mm文件中的6095行。

同样的我们通过汇编代码执行顺序依然可以验证__objc_msgSend_uncached - MethodTableLookup - _lookUpImpOrForward,这里我就不再走一遍了。

接下来我们迫不及待的进入lookUpImpOrForward方法一探究竟.

lookUpImpOrForward源码(重点)
IMP lookUpImpOrForward(id inst, SEL sel, Class cls, int behavior)
{
    const IMP forward_imp = (IMP)_objc_msgForward_impcache;
    IMP imp = nil;
    Class curClass;

    runtimeLock.assertUnlocked();

    // Optimistic cache lookup
    if (fastpath(behavior & LOOKUP_CACHE)) {
        imp = cache_getImp(cls, sel);
        if (imp) goto done_nolock;
    }

    // runtimeLock is held during isRealized and isInitialized checking
    // to prevent races against concurrent realization.

    // runtimeLock is held during method search to make
    // method-lookup + cache-fill atomic with respect to method addition.
    // Otherwise, a category could be added but ignored indefinitely because
    // the cache was re-filled with the old value after the cache flush on
    // behalf of the category.

    runtimeLock.lock();

    // We don't want people to be able to craft a binary blob that looks like
    // a class but really isn't one and do a CFI attack.
    //
    // To make these harder we want to make sure this is a class that was
    // either built into the binary or legitimately registered through
    // objc_duplicateClass, objc_initializeClassPair or objc_allocateClassPair.
    //
    // TODO: this check is quite costly during process startup.
    checkIsKnownClass(cls);

    if (slowpath(!cls->isRealized())) {
        cls = realizeClassMaybeSwiftAndLeaveLocked(cls, runtimeLock);
        // runtimeLock may have been dropped but is now locked again
    }

    if (slowpath((behavior & LOOKUP_INITIALIZE) && !cls->isInitialized())) {
        cls = initializeAndLeaveLocked(cls, inst, runtimeLock);
        // runtimeLock may have been dropped but is now locked again

        // If sel == initialize, class_initialize will send +initialize and 
        // then the messenger will send +initialize again after this 
        // procedure finishes. Of course, if this is not being called 
        // from the messenger then it won't happen. 2778172
    }

    runtimeLock.assertLocked();
    curClass = cls;

    // The code used to lookpu 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().

    for (unsigned attempts = unreasonableClassCount();;) {
        // curClass method list.
        Method meth = getMethodNoSuper_nolock(curClass, sel);
        if (meth) {
            imp = meth->imp;
            goto done;
        }

        if (slowpath((curClass = curClass->superclass) == 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)) {
        behavior ^= LOOKUP_RESOLVER;
        return resolveMethod_locked(inst, sel, cls, behavior);
    }

 done:
    log_and_fill_cache(cls, imp, sel, inst, curClass);
    runtimeLock.unlock();
 done_nolock:
    if (slowpath((behavior & LOOKUP_NIL) && imp == forward_imp)) {
        return nil;
    }
    return imp;
}

这里我们详细的分析一下

    const IMP forward_imp = (IMP)_objc_msgForward_impcache;
    IMP imp = nil;
    Class curClass;
    runtimeLock.assertUnlocked();

    // Optimistic cache lookup
    if (fastpath(behavior & LOOKUP_CACHE)) {
        imp = cache_getImp(cls, sel);
        if (imp) goto done_nolock;
    }
    //加锁,目的是保证读取的线程安全
    runtimeLock.lock();

这里主要初始化了一些变量,其中forward_imp的值就是未找到imp的函数实现。需要注意的是,这里我们又从缓存中查找了一次imp = cache_getImp(cls, sel);,原因是因为有可能我们在慢速查找的时候,有可能通过之前分析的汇编代码缓存,又缓存了这个方法(因为汇编代码的执行效率比较高),所以需要再查找一次缓存。

checkIsKnownClass源码

ALWAYS_INLINE
static void
checkIsKnownClass(Class cls)
{
    if (slowpath(!isKnownClass(cls))) {
        _objc_fatal("Attempt to use unknown class %p.", cls);
    }
}

这里是检测传入的类是否是已知类,只有合法的类才能进行下面的方法查找

// 如果类没有实现
if (slowpath(!cls->isRealized())) {
      // 检查类是不是swift类(如果是,就返回swift类)
      // 在赋值时会解开运行时锁,在返回时会继续加上运行时锁
     cls = realizeClassMaybeSwiftAndLeaveLocked(cls, runtimeLock);
     // runtimeLock may have been dropped but is now locked again
}

这里类没有实现,我们就需要去做类的实现,我们一步步跟入到oc类最终的实现方法realizeClassWithoutSwift,而Swift类的话会调用cls = realizeSwiftClass(cls);方法,两者实现的功能基本一致,这里我们以oc的为例展开分析

realizeClassWithoutSwift源码

static Class realizeClassWithoutSwift(Class cls, Class previously)
{
    runtimeLock.assertLocked();

    class_rw_t *rw;
    Class supercls;
    Class metacls;

  ...省略一些代码

    // Realize superclass and metaclass, if they aren't already.
    // This needs to be done after RW_REALIZED is set above, for root classes.
    // This needs to be done after class index is chosen, for root metaclasses.
    // This assumes that none of those classes have Swift contents,
    //   or that Swift's initializers have already been called.
    //   fixme that assumption will be wrong if we add support
    //   for ObjC subclasses of Swift classes.

    //  递归父类和元类
    supercls = realizeClassWithoutSwift(remapClass(cls->superclass), nil);
    metacls = realizeClassWithoutSwift(remapClass(cls->ISA()), nil);

    // Update superclass and metaclass in case of remapping
   //更新继承关系
    cls->superclass = supercls;
   //更新isa指向
    cls->initClassIsa(metacls);

    ...省略一些代码

    // Attach categories
    methodizeClass(cls, previously);

    return cls;
}

这里主要是为我们查找方法做准备工作:

  • 一方面将类中的ro,rw以及data()中的数据进行赋值,确保数据无误
  • 另一方面将我们的传入的类继承关系isa的指向关系进行更新,同时与该类的categories绑定,接着将传入的类的父类一直到nil进行相同的操作,确保我们在查找继承链中方法的时候不会出错
    这里realizeClassWithoutSwift方法的递归调用也是值得我们学习的地方
if (slowpath((behavior & LOOKUP_INITIALIZE) && !cls->isInitialized())) {
        cls = initializeAndLeaveLocked(cls, inst, runtimeLock);
        // runtimeLock may have been dropped but is now locked again

        // If sel == initialize, class_initialize will send +initialize and 
        // then the messenger will send +initialize again after this 
        // procedure finishes. Of course, if this is not being called 
        // from the messenger then it won't happen. 2778172
    }

这里调用我们熟悉的initialize初始化方法,也就解释了initialize会自动调用的原因,原来是系统为我们调用的。

查找方法

// unreasonableClassCount -- 表示类的迭代的上限
for (unsigned attempts = unreasonableClassCount();;) {
        // curClass method list.
        //在本类中查找
        Method meth = getMethodNoSuper_nolock(curClass, sel);
       //如果找到,goto done;
        if (meth) {
            imp = meth->imp;
            goto done;
        }
       //如果没有找到, curClass赋值为curClass的父类,如果父类为nil,说明未找到该imp
        if (slowpath((curClass = curClass->superclass) == nil)) {
            // No implementation found, and method resolver didn't help.
            // Use forwarding.
            imp = forward_imp;
            break;
        }
        //--attempts=0 说明循环已到上限,进行报错
        // 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)) {
            //一直找不到直接break退出for循环
            // 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)) {
            //找到了此方法,将其存储到cache中
            // 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)) {
        behavior ^= LOOKUP_RESOLVER;
        return resolveMethod_locked(inst, sel, cls, behavior);
    }

 done:
    //加入缓存,方便下次直接取
    log_and_fill_cache(cls, imp, sel, inst, curClass);
    runtimeLock.unlock();
 done_nolock:
    if (slowpath((behavior & LOOKUP_NIL) && imp == forward_imp)) {
        return nil;
    }
    return imp;

这里我加了一些注释方便大家理解

其中查找本类的getMethodNoSuper_nolock方法,为二分查找,最后会调用到findMethodInSortedMethodList方法,这里我们进入源码看下

findMethodInSortedMethodList(SEL key, const method_list_t *list)
{
    ASSERT(list);

    const method_t * const first = &list->first;
    const method_t *base = first;//首地址
    const method_t *probe;
    uintptr_t keyValue = (uintptr_t)key;//需要查找方法的地址
    uint32_t count;
    
    for (count = list->count; count != 0; count >>= 1) {
        probe = base + (count >> 1);//count右移一位,即除以2,二分查找
        
        uintptr_t probeValue = (uintptr_t)probe->name;
         // 如果找到方法通过while-- 排除分类重名方法
        if (keyValue == probeValue) {
            // `probe` is a match.
            // Rewind looking for the *first* occurrence of this value.
            // This is required for correct category overrides.
      
           //排除分类重名方法(方法的存储是先存储类方法,在存储分类---按照先进后出的原则,分类方法最先出,而我们要取的类方法,所以需要先排除分类方法)
                //如果是两个分类,就看谁先进行加载
            while (probe > first && keyValue == (uintptr_t)probe[-1].name) {
                probe--;
            }
            return (method_t *)probe;
        }
        
        if (keyValue > probeValue) {
            base = probe + 1;
            count--;
        }
    }
    
    return nil;
}

其中cache_getImp方法为汇编代码,源码如下:

    STATIC_ENTRY _cache_getImp
    GetClassFromIsa_p16 p0
    CacheLookup GETIMP, _cache_getImp

    .macro GetClassFromIsa_p16 /* src */
    __LP64__
    // 64-bit packed isa
    and p16, $0, #ISA_MASK
    .endmacro

这里是有必要解释一下,cache_getImp是从父类的缓存中找我们传入的sel方法,如果父类中缓存没有找到,就break跳出for循环。如果父类中缓存中找到了这个方法,就加到本类的缓存中,方便下次直接快速查找。(细心的你可能已经发现for循环里只有一个条件,相当于是一个死循环

查找流程总结

  • 通过getMethodNoSuper_nolock在本类中查找meth,如果找到methgoto done缓存起来
  • 在上面没找到meth,通过curClass = curClass->superclasscurClass指向父类,如果curClassnil,说明找不到方法,imp = forward_imp然后结束循环
  • --attempts=0说明循环已到上限,进行报错
  • 通过cache_getImp(curClass, sel)方法在父类缓存中查找,如果找不到就break跳出for循环。如果父类中缓存中找到了这个方法,就加到本类的缓存中,方便下次直接快速查找
  • 如果imp == forward_imp,结束循环
  • 如果没找到方法,循环结束后会进入resolveMethod_locked去做一次动态决议。

动态决议resolveMethod_locked

// No implementation found. Try method resolver once.

if (slowpath(behavior & LOOKUP_RESOLVER)) {
  behavior ^= LOOKUP_RESOLVER;
  return resolveMethod_locked(inst, sel, cls, behavior);
}

...分割线

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

    runtimeLock.unlock();
   //实例方法调用
    if (! cls->isMetaClass()) {
        // try [cls resolveInstanceMethod:sel]
        resolveInstanceMethod(inst, sel, cls);
    } 
    else {
      
        // try [nonMetaClass resolveClassMethod:sel]
        // and [cls resolveInstanceMethod:sel]
       //类方法调用
        resolveClassMethod(inst, sel, cls);
        if (!lookUpImpOrNil(inst, sel, cls)) {
            resolveInstanceMethod(inst, sel, cls);
        }
    }

    // chances are that calling the resolver have populated the cache
    // so attempt using it
    return lookUpImpOrForward(inst, sel, cls, behavior | LOOKUP_CACHE);
}

这里实例方法会调用resolveInstanceMethod,类方法会调用resolveClassMethod

resolveInstanceMethod

static void resolveInstanceMethod(id inst, SEL sel, Class cls)
{
    runtimeLock.assertUnlocked();
    ASSERT(cls->isRealized());
    SEL resolve_sel = @selector(resolveInstanceMethod:);
    // 1\. 判断系统是否实现SEL_resolveInstanceMethod方法
    // 即+(BOOL)resolveInstanceMethod:(SEL)sel,
    // 继承自NSObject的类,默认实现,返回NO
    if (!lookUpImpOrNil(cls, resolve_sel, cls->ISA())) {
        // Resolver not implemented.
        // 不是NSObject的子类,也未实现+(BOOL)resolveInstanceMethod:(SEL)sel,
        // 直接返回,没有动态解析的必要
        return;
    }
    //系统给你一次机会 - 你要不要针对 sel 来操作一下下
    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
    //再次寻找IMP
    IMP imp = lookUpImpOrNil(inst, sel, cls);
    // 只有这用了resolved, 所以返回NO或YES不影响forward
    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));
        }
    }
}

resolveClassMethod

static void resolveClassMethod(id inst, SEL sel, Class cls)
{
    runtimeLock.assertUnlocked();
    ASSERT(cls->isRealized());
    ASSERT(cls->isMetaClass());

    if (!lookUpImpOrNil(inst, @selector(resolveClassMethod:), cls)) {
        // Resolver not implemented.
        return;
    }

    Class nonmeta;
    {
        mutex_locker_t lock(runtimeLock);
        nonmeta = getMaybeUnrealizedNonMetaClass(cls, inst);
        // +initialize path should have realized nonmeta already
        if (!nonmeta->isRealized()) {
            _objc_fatal("nonmeta class %s (%p) unexpectedly not realized",
                        nonmeta->nameForLogging(), nonmeta);
        }
    }
    BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
    bool resolved = msg(nonmeta, @selector(resolveClassMethod:), sel);

    // Cache the result (good or bad) so the resolver doesn't fire next time.
    // +resolveClassMethod adds to self->ISA() a.k.a. cls
    IMP imp = lookUpImpOrNil(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 resolveClassMethod:%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));
        }
    }
}

由此:我们可以在+(BOOL)resolveInstanceMethod:(SEL)sel方法中对未实现的方法指定已实现方法的IMP,并添加到类中,实现方法动态解析,防止系统崩溃

我们将ZGPersontest4的实现删除,然后利用动态决议对test4处理,然后在main中调用test4

- (void)test1{
    NSLog(@"%s",__func__);
}
- (void)test2{
    NSLog(@"%s",__func__);
}
- (void)test3{
    NSLog(@"%s",__func__);
}

+ (void)testClass{
    NSLog(@"%s",__func__);
}
+ (BOOL)resolveInstanceMethod:(SEL)sel{
    
    if (sel == @selector(test4)) {
        NSLog(@"%@ 来了",NSStringFromSelector(sel));
        
        IMP imp           = class_getMethodImplementation(self, @selector(test1));
        Method sayMMethod = class_getInstanceMethod(self, @selector(test1));
        const char *type  = method_getTypeEncoding(sayMMethod);
        return class_addMethod(self, sel, imp, type);
    }
    
    return [super resolveInstanceMethod:sel];
}

...分割线

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // insert code here...
        ZGPerson *person = [ZGPerson alloc];
//        [person test1];
        [person test4];
    }
    
    return 0;
}

执行结果如下


动态决议执行

并没有崩溃,而是调用了我们写的test1的实现,由此验证了上面的结论,

objc_msgForward_impcache做了什么?

如果我们方法决议仍然没有处理,那么const IMP forward_imp = (IMP)_objc_msgForward_impcache;,走到objc_msgForward_impcache方法,objc_msgForward_impcache汇编实现,我们仍然以arm64的为例

    STATIC_ENTRY __objc_msgForward_impcache

    // No stret specialization.
        // 跳转到__objc_msgForward
    b   __objc_msgForward

    END_ENTRY __objc_msgForward_impcache

    ENTRY __objc_msgForward

    adrp    x17, __objc_forward_handler@PAGE
    ldr p17, [x17, __objc_forward_handler@PAGEOFF]
    TailCallFunctionPointer x17
    
    END_ENTRY __objc_msgForward

_objc_msgForward_impcache 方法 调用 __objc_msgForward方法,__objc_msgForward方法 调用 __objc_forward_handler方法,
这里我们锁定了汇编方法__objc_forward_handler,全局搜索发现找不到,原来又是c++,去掉一个_,最终找到了__objc_forward_handler方法的具体实现。

// Default forward handler halts the process.
__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;

就是我们在开发中经常遇到的错误unrecognized selector...

总结

  • 当在·objc_msgSend缓存中没有找到方法,就会来到 CheckMiss或者 JumpMiss -> __objc_msgSend_uncached -> MethodTableLookup -> lookUpImpOrForward进行慢速查找流程。
  • 方法慢速查找会先在本类方法列表中找,找不到再去父类缓存中找,父类缓存没有就去父类方法列表找,一直找到父类为nil
  • 如果慢速查找都没有找到,就会进行动态决议_class_resolveMethod,这是苹果给我们的最后一次处理机会。
  • 动态决议不处理,最后就会走到 将imp 置换成forward_imp,最终到_objc_forward_handler 方法 崩溃报错 unrecognized selector sent to instance ...

你可能感兴趣的:(objc_msgSend 流程之慢速查找)