阅读 127

iOS OC类底层objc_msgSend分析下(慢速查找流程)

1. imp快速查找流程回顾

  上一篇文章我们已经详细探讨了imp的快速查找流程,但是还未验证所探讨出来的imp快速查找流程的正确性,因此,现在就创建一个APP工程,运行到真机上,查看一下汇编流程。

  • 创建工程

image.png

  • 创建一个Person类,其代码如下所示:
//Person.h文件
@interface Person : NSObject

@property (nonatomic, copy) NSString *name;

- (void)saySomething:(NSString *)worlds;

- (void)sayHi;

- (void)sayHi2;

@end


NS_ASSUME_NONNULL_END

//Person.m文件
#import "Person.h"

@implementation Person

- (void)saySomething:(NSString *)worlds {
    NSLog(@"%@ --- %s", worlds, __FUNCTION__);
}

- (void)sayHi {
    
    NSLog(@"%s", __FUNCTION__);
}

- (void)sayHi2 {
    
    NSLog(@"%s", __FUNCTION__);
}

@end

复制代码
  • ViewController.m文件的ViewDidLoad方法中创建Person类对象并调用其这两个实例方法,在调用sayHi2方法之前打上断点,如下图所示:

image.png

  • 连接真机,编译运行程序,执行到断点的时候,显示汇编代码,如下图所示:

image.png

  • 单步运行程序

image.png

image.png

  • 继续单步执行,进入到objc_msgSend汇编部分,输出x0寄存器的值以及[Person class]的值,看是否一致,如下图所示:

image.png

  • 汇编分析

image.png

image.png

image.png

image.png

2. imp慢速查找流程探究

2.1 从汇编回到C++代码

  经过对objc_msgSend汇编代码的静态探究以及动态验证,我们已经对方法的快速查找流程有了相当深入的了解,紧接着,我们就来探究一下当快速流程无法查找到imp,底层又是如何处理的。   首先,紧接着快速查找流程的探索,我们在源码objc-msg-arm64.s文件中搜索__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
	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
	mov	x2, x16
	mov	x3, #3
	bl	_lookUpImpOrForward

	// IMP in x0
	mov	x17, x0

	RESTORE_REGS MSGSEND

.endmacro
复制代码

  在这个宏定义中,又调用了SAVE_REGS这个宏,这个宏是为了存储消息发送中产生的一些环境变量,之后又执行了汇编代码bl _lookUpImpOrForwardbl是跳转指令,一般是跳转到某个函数继续执行代码,因此_lookUpImpOrForward应该是某个C++函数的符号,上面的注释中表示这个函数有四个参数,第一个是receiver,第二个是_cmd,第三个是cls,第四个是3,搜索lookUpImpOrForward这个函数,其代码如下所示:

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

    if (slowpath(!cls->isInitialized())) {
        // The first message sent to a class is often +new or +alloc, or +self
        // which goes through objc_opt_* or various optimized entry points.
        //
        // However, the class isn't realized/initialized yet at this point,
        // and the optimized entry points fall down through objc_msgSend,
        // which ends up here.
        //
        // We really want to avoid caching these, as it can cause IMP caches
        // to be made with a single entry forever.
        //
        // Note that this check is racy as several threads might try to
        // message a given class for the first time at the same time,
        // in which case we might cache anyway.
        behavior |= LOOKUP_NOCACHE;
    }

    // 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.
    checkIsKnownClass(cls);

    cls = realizeAndInitializeIfNeeded_locked(inst, cls, behavior & LOOKUP_INITIALIZE);
    // runtimeLock may have been dropped but is now locked again
    runtimeLock.assertLocked();
    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().

    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)) {
        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();
    if (slowpath((behavior & LOOKUP_NIL) && imp == forward_imp)) {
        return nil;
    }
    return imp;
}

复制代码

  在这个方法中,先是一个if语句,判断这个cls有没有初始化,如果未初始化,behavior(初始值为3|= LOOKUP_NOCACHE8),然后再调用checkIsKnownClass函数,这个函数具体代码如下:

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

  然后调用了isKnownClass函数,这个函数代码如下:

static bool
isKnownClass(Class cls)
{
    if (fastpath(objc::dataSegmentsRanges.contains(cls->data()->witness, (uintptr_t)cls))) {
        return true;
    }
    auto &set = objc::allocatedClasses.get();
    return set.find(cls) != set.end() || dataSegmentsContain(cls);
}

复制代码

  这个函数中,初始化了一个allocatedClasses表(表里面有所有已分配的Class),然后遍历这个表,如果查找到了传入的cls或者数据段也包含这个cls,就返回true。实际上checkIsKnownClass函数是为了防止其他人制作一个类似但实际上不是类的class,然后做一个CFI进行攻击,为了使这些操作变得更难,就只能确保这是一个类,要么内置到二进制中,要么通过合法注册。

2.2 递归实现初始化Class、Class的超类以及元类

  检查类之后,又调用了realizeAndInitializeIfNeeded_locked函数对cls进行赋值,这个函数具体代码如下:

/***********************************************************************
* realizeAndInitializeIfNeeded_locked
* Realize the given class if not already realized, and initialize it if
* not already initialized.
* inst is an instance of cls or a subclass, or nil if none is known.
* cls is the class to initialize and realize.
* initializer is true to initialize the class, false to skip initialization.
**********************************************************************/
static Class
realizeAndInitializeIfNeeded_locked(id inst, Class cls, bool initialize)
{
    runtimeLock.assertLocked();
    if (slowpath(!cls->isRealized())) {
        cls = realizeClassMaybeSwiftAndLeaveLocked(cls, runtimeLock);
        // runtimeLock may have been dropped but is now locked again
    }

    if (slowpath(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
    }
    return cls;
}
复制代码

  这个函数主要是用来实现并且初始化类的,首先先来看看realizeClassMaybeSwiftAndLeaveLocked函数的实现,如下所示:

static Class
realizeClassMaybeSwiftAndLeaveLocked(Class cls, mutex_t& lock)
{
    return realizeClassMaybeSwiftMaybeRelock(cls, lock, true);
}
复制代码

  realizeClassMaybeSwiftMaybeRelock函数的实现如下:



/***********************************************************************
* realizeClassMaybeSwift (MaybeRelock / AndUnlock / AndLeaveLocked)
* Realize a class that might be a Swift class.
* Returns the real class structure for the class. 
* Locking: 
*   runtimeLock must be held on entry
*   runtimeLock may be dropped during execution
*   ...AndUnlock function leaves runtimeLock unlocked on exit
*   ...AndLeaveLocked re-acquires runtimeLock if it was dropped
* This complication avoids repeated lock transitions in some cases.
**********************************************************************/
static Class
realizeClassMaybeSwiftMaybeRelock(Class cls, mutex_t& lock, bool leaveLocked)
{
    lock.assertLocked();

    if (!cls->isSwiftStable_ButAllowLegacyForNow()) {
        // Non-Swift class. Realize it now with the lock still held.
        // fixme wrong in the future for objc subclasses of swift classes
        realizeClassWithoutSwift(cls, nil);
        if (!leaveLocked) lock.unlock();
    } else {
        // Swift class. We need to drop locks and call the Swift
        // runtime to initialize it.
        lock.unlock();
        cls = realizeSwiftClass(cls);
        ASSERT(cls->isRealized());    // callback must have provoked realization
        if (leaveLocked) lock.lock();
    }

    return cls;
}
复制代码

  这个函数中会判断是否是一个Swift Class,如果不是Swift Class,就会调用realizeClassWithoutSwift函数,其代码如下所示:

/***********************************************************************
* realizeClassWithoutSwift
* Performs first-time initialization on class cls, 
* including allocating its read-write data.
* Does not perform any Swift-side initialization.
* Returns the real class structure for the class. 
* Locking: runtimeLock must be write-locked by the caller
**********************************************************************/
static Class realizeClassWithoutSwift(Class cls, Class previously)
{
    runtimeLock.assertLocked();

    class_rw_t *rw;
    Class supercls;
    Class metacls;

    if (!cls) return nil;
    if (cls->isRealized()) {
        validateAlreadyRealizedClass(cls);
        return cls;
    }
    ASSERT(cls == remapClass(cls));

    // fixme verify class is not in an un-dlopened part of the shared cache?

    auto ro = (const class_ro_t *)cls->data();
    auto isMeta = ro->flags & RO_META;
    if (ro->flags & RO_FUTURE) {
        // This was a future class. rw data is already allocated.
        rw = cls->data();
        ro = cls->data()->ro();
        ASSERT(!isMeta);
        cls->changeInfo(RW_REALIZED|RW_REALIZING, RW_FUTURE);
    } else {
        // Normal class. Allocate writeable class data.
        rw = objc::zalloc<class_rw_t>();
        rw->set_ro(ro);
        rw->flags = RW_REALIZED|RW_REALIZING|isMeta;
        cls->setData(rw);
    }

    cls->cache.initializeToEmptyOrPreoptimizedInDisguise();

#if FAST_CACHE_META
    if (isMeta) cls->cache.setBit(FAST_CACHE_META);
#endif

    // Choose an index for this class.
    // Sets cls->instancesRequireRawIsa if indexes no more indexes are available
    cls->chooseClassArrayIndex();

    if (PrintConnecting) {
        _objc_inform("CLASS: realizing class '%s'%s %p %p #%u %s%s",
                     cls->nameForLogging(), isMeta ? " (meta)" : "", 
                     (void*)cls, ro, cls->classArrayIndex(),
                     cls->isSwiftStable() ? "(swift)" : "",
                     cls->isSwiftLegacy() ? "(pre-stable swift)" : "");
    }

    // 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->getSuperclass()), nil);
    metacls = realizeClassWithoutSwift(remapClass(cls->ISA()), nil);

#if SUPPORT_NONPOINTER_ISA
    if (isMeta) {
        // Metaclasses do not need any features from non pointer ISA
        // This allows for a faspath for classes in objc_retain/objc_release.
        cls->setInstancesRequireRawIsa();
    } else {
        // Disable non-pointer isa for some classes and/or platforms.
        // Set instancesRequireRawIsa.
        bool instancesRequireRawIsa = cls->instancesRequireRawIsa();
        bool rawIsaIsInherited = false;
        static bool hackedDispatch = false;

        if (DisableNonpointerIsa) {
            // Non-pointer isa disabled by environment or app SDK version
            instancesRequireRawIsa = true;
        }
        else if (!hackedDispatch  &&  0 == strcmp(ro->getName(), "OS_object"))
        {
            // hack for libdispatch et al - isa also acts as vtable pointer
            hackedDispatch = true;
            instancesRequireRawIsa = true;
        }
        else if (supercls  &&  supercls->getSuperclass()  &&
                 supercls->instancesRequireRawIsa())
        {
            // This is also propagated by addSubclass()
            // but nonpointer isa setup needs it earlier.
            // Special case: instancesRequireRawIsa does not propagate
            // from root class to root metaclass
            instancesRequireRawIsa = true;
            rawIsaIsInherited = true;
        }

        if (instancesRequireRawIsa) {
            cls->setInstancesRequireRawIsaRecursively(rawIsaIsInherited);
        }
    }
// SUPPORT_NONPOINTER_ISA
#endif

    // Update superclass and metaclass in case of remapping
    cls->setSuperclass(supercls);
    cls->initClassIsa(metacls);

    // Reconcile instance variable offsets / layout.
    // This may reallocate class_ro_t, updating our ro variable.
    if (supercls  &&  !isMeta) reconcileInstanceVariables(cls, supercls, ro);

    // Set fastInstanceSize if it wasn't set already.
    cls->setInstanceSize(ro->instanceSize);

    // Copy some flags from ro to rw
    if (ro->flags & RO_HAS_CXX_STRUCTORS) {
        cls->setHasCxxDtor();
        if (! (ro->flags & RO_HAS_CXX_DTOR_ONLY)) {
            cls->setHasCxxCtor();
        }
    }
    
    // Propagate the associated objects forbidden flag from ro or from
    // the superclass.
    if ((ro->flags & RO_FORBIDS_ASSOCIATED_OBJECTS) ||
        (supercls && supercls->forbidsAssociatedObjects()))
    {
        rw->flags |= RW_FORBIDS_ASSOCIATED_OBJECTS;
    }

    // Connect this class to its superclass's subclass lists
    if (supercls) {
        addSubclass(supercls, cls);
    } else {
        addRootClass(cls);
    }

    // Attach categories
    methodizeClass(cls, previously);

    return cls;
}
复制代码

  这个函数是实现Class(主要是为了给Class中的class_rw_t以及class_ro_t结构体中的数据赋值,就是Class的实例变量列表、方法列表),并且会递归调用这个函数实现其父类以及元类,并给Class的父类以及Metaclass赋值,系统为什么会这么做呢,因为慢速查找流程实际上是遍历Class中的Methods_list中进行查找,但如果在Class中查找不到就会结束查找吗?肯定不会的,因为Class的实例对象调用的也可能是父类中定义的方法,所以也要遍历父类中的Methods_list查找方法,而类方法也是如此。 实现完Class之后,就会进行Class的初始化,初始化调用的函数是initializeAndLeaveLocked,具体代码如下:

// Locking: caller must hold runtimeLock; this may drop and re-acquire it
static Class initializeAndLeaveLocked(Class cls, id obj, mutex_t& lock)
{
    return initializeAndMaybeRelock(cls, obj, lock, true);
}
复制代码

  而initializeAndMaybeRelock函数中代码如下所示:

/***********************************************************************
* class_initialize.  Send the '+initialize' message on demand to any
* uninitialized class. Force initialization of superclasses first.
* inst is an instance of cls, or nil. Non-nil is better for performance.
* Returns the class pointer. If the class was unrealized then 
* it may be reallocated.
* Locking: 
*   runtimeLock must be held by the caller
*   This function may drop the lock.
*   On exit the lock is re-acquired or dropped as requested by leaveLocked.
**********************************************************************/
static Class initializeAndMaybeRelock(Class cls, id inst,
                                      mutex_t& lock, bool leaveLocked)
{
    lock.assertLocked();
    ASSERT(cls->isRealized());

    if (cls->isInitialized()) {
        if (!leaveLocked) lock.unlock();
        return cls;
    }

    // Find the non-meta class for cls, if it is not already one.
    // The +initialize message is sent to the non-meta class object.
    Class nonmeta = getMaybeUnrealizedNonMetaClass(cls, inst);

    // Realize the non-meta class if necessary.
    if (nonmeta->isRealized()) {
        // nonmeta is cls, which was already realized
        // OR nonmeta is distinct, but is already realized
        // - nothing else to do
        lock.unlock();
    } else {
        nonmeta = realizeClassMaybeSwiftAndUnlock(nonmeta, lock);
        // runtimeLock is now unlocked
        // fixme Swift can't relocate the class today,
        // but someday it will:
        cls = object_getClass(nonmeta);
    }

    // runtimeLock is now unlocked, for +initialize dispatch
    ASSERT(nonmeta->isRealized());
    initializeNonMetaClass(nonmeta);

    if (leaveLocked) runtimeLock.lock();
    return cls;
}
复制代码

  这个函数的作用就是按需发送'+initialize'消息给消息给任意未初始化的类。首先强制初始化超类,参数instcls的一个实例,也可以为nil,返回一个类指针,如果这个类没有被实现可以重新分配。此函数可能会取消锁(runtimeLock必须由调用者持有),在退出时,按照leaveLocked的请求重新获取或删除锁。在函数体中,首先判断这个cls是否已经初始化,已初始化就直接返回这个cls,如果没有初始化就找到cls的非元类(如果cls是一个元类),获取到nonmeta之后,还会再次判断这个非元类是否已被实现,如果未实现就调用realizeClassMaybeSwiftAndUnlock函数进行实现,之后调用initializeNonMetaClass函数进行nonmeta的初始化,这个函数代码如下所示:

/***********************************************************************
* class_initialize.  Send the '+initialize' message on demand to any
* uninitialized class. Force initialization of superclasses first.
**********************************************************************/
void initializeNonMetaClass(Class cls)
{
    ASSERT(!cls->isMetaClass());

    Class supercls;
    bool reallyInitialize = NO;

    // Make sure super is done initializing BEFORE beginning to initialize cls.
    // See note about deadlock above.
    supercls = cls->getSuperclass();
    if (supercls  &&  !supercls->isInitialized()) {
        initializeNonMetaClass(supercls);
    }
    
    // Try to atomically set CLS_INITIALIZING.
    SmallVector<_objc_willInitializeClassCallback, 1> localWillInitializeFuncs;
    {
        monitor_locker_t lock(classInitLock);
        if (!cls->isInitialized() && !cls->isInitializing()) {
            cls->setInitializing();
            reallyInitialize = YES;

            // Grab a copy of the will-initialize funcs with the lock held.
            localWillInitializeFuncs.initFrom(willInitializeFuncs);
        }
    }
    
    if (reallyInitialize) {
        // We successfully set the CLS_INITIALIZING bit. Initialize the class.
        
        // Record that we're initializing this class so we can message it.
        _setThisThreadIsInitializingClass(cls);

        if (MultithreadedForkChild) {
            // LOL JK we don't really call +initialize methods after fork().
            performForkChildInitialize(cls, supercls);
            return;
        }
        
        for (auto callback : localWillInitializeFuncs)
            callback.f(callback.context, cls);

        // Send the +initialize message.
        // Note that +initialize is sent to the superclass (again) if 
        // this class doesn't implement +initialize. 2157218
        if (PrintInitializing) {
            _objc_inform("INITIALIZE: thread %p: calling +[%s initialize]",
                         objc_thread_self(), cls->nameForLogging());
        }

        // Exceptions: A +initialize call that throws an exception 
        // is deemed to be a complete and successful +initialize.
        //
        // Only __OBJC2__ adds these handlers. !__OBJC2__ has a
        // bootstrapping problem of this versus CF's call to
        // objc_exception_set_functions().
#if __OBJC2__
        @try
#endif
        {
            callInitialize(cls);

            if (PrintInitializing) {
                _objc_inform("INITIALIZE: thread %p: finished +[%s initialize]",
                             objc_thread_self(), cls->nameForLogging());
            }
        }
#if __OBJC2__
        @catch (...) {
            if (PrintInitializing) {
                _objc_inform("INITIALIZE: thread %p: +[%s initialize] "
                             "threw an exception",
                             objc_thread_self(), cls->nameForLogging());
            }
            @throw;
        }
        @finally
#endif
        {
            // Done initializing.
            lockAndFinishInitializing(cls, supercls);
        }
        return;
    }
    
    else if (cls->isInitializing()) {
        // We couldn't set INITIALIZING because INITIALIZING was already set.
        // If this thread set it earlier, continue normally.
        // If some other thread set it, block until initialize is done.
        // It's ok if INITIALIZING changes to INITIALIZED while we're here, 
        //   because we safely check for INITIALIZED inside the lock 
        //   before blocking.
        if (_thisThreadIsInitializingClass(cls)) {
            return;
        } else if (!MultithreadedForkChild) {
            waitForInitializeToComplete(cls);
            return;
        } else {
            // We're on the child side of fork(), facing a class that
            // was initializing by some other thread when fork() was called.
            _setThisThreadIsInitializingClass(cls);
            performForkChildInitialize(cls, supercls);
        }
    }
    
    else if (cls->isInitialized()) {
        // Set CLS_INITIALIZING failed because someone else already 
        //   initialized the class. Continue normally.
        // NOTE this check must come AFTER the ISINITIALIZING case.
        // Otherwise: Another thread is initializing this class. ISINITIALIZED 
        //   is false. Skip this clause. Then the other thread finishes 
        //   initialization and sets INITIALIZING=no and INITIALIZED=yes. 
        //   Skip the ISINITIALIZING clause. Die horribly.
        return;
    }
    
    else {
        // We shouldn't be here. 
        _objc_fatal("thread-safe class init in objc runtime is buggy!");
    }
}
复制代码

  在这个函数中,首先会进行一次断言,cls是非元类才能继续执行代码,然后会递归调用本函数,进行超类的初始化,然后再尝试自动设置CLS_INITIALIZING,如果类未初始化完成并且类不在初始化中,就会调用callInitialize函数进行初始化,这个函数的代码如下所示:

void callInitialize(Class cls)
{
    ((void(*)(Class, SEL))objc_msgSend)(cls, @selector(initialize));
    asm("");
}
复制代码

  这个初始化方法实际上是调用了objc_msgSend函数发送了一个initialize消息给cls,看到这里,我们大概就清楚的了解了Class初始化的流程了。

2.3 二分查找method

  在递归实现以及初始化Class之后,就会继续执行lookUpImpOrForward函数中for循环的代码,但是这个for循环很特别,没有条件语句,也没有自增自减语句,因此,如果没有跳出循环的语句(break, goto),这就是个死循环,for循环中首先调用了curClass->cache.isConstantOptimizedCache(/* strict */true)函数来判断是否不断地优化缓存,这个函数在非真机环境下,返回的是false,因此会执行getMethodNoSuper_nolock函数查找curClassMethods_list是否有sel对应的函数实现imp,这个函数代码如下所示:

/***********************************************************************
 * getMethodNoSuper_nolock
 * fixme
 * Locking: runtimeLock must be read- or write-locked by the caller
 **********************************************************************/
static method_t *
getMethodNoSuper_nolock(Class cls, SEL sel)
{
    runtimeLock.assertLocked();

    ASSERT(cls->isRealized());
    // fixme nil cls? 
    // fixme nil sel?

    auto const methods = cls->data()->methods();
    for (auto mlists = methods.beginLists(),
              end = methods.endLists();
         mlists != end;
         ++mlists)
    {
        // <rdar://problem/46904873> getMethodNoSuper_nolock is the hottest
        // caller of search_method_list, inlining it turns
        // getMethodNoSuper_nolock into a frame-less function and eliminates
        // any store from this codepath.
        method_t *m = search_method_list_inline(*mlists, sel);
        if (m) return m;
    }

    return nil;
}
复制代码

  这个函数中首先调用获取clsmethods函数获取方法列表,然后遍历methods中所有的方法,但是这个methods函数返回的值是一个二维的,因此又会调用search_method_list_inline函数查找这个二维数组中每个一维方法数组列表,其代码如下所示:

ALWAYS_INLINE static method_t *
search_method_list_inline(const method_list_t *mlist, SEL sel)
{
    int methodListIsFixedUp = mlist->isFixedUp();
    int methodListHasExpectedSize = mlist->isExpectedSize();
    
    if (fastpath(methodListIsFixedUp && methodListHasExpectedSize)) {
        return findMethodInSortedMethodList(sel, mlist);
    } else {
        // Linear search of unsorted method list
        if (auto *m = findMethodInUnsortedMethodList(sel, mlist))
            return m;
    }

#if DEBUG
    // sanity-check negative results
    if (mlist->isFixedUp()) {
        for (auto& meth : *mlist) {
            if (meth.name() == sel) {
                _objc_fatal("linear search worked when binary search did not");
            }
        }
    }
#endif

    return nil;
}
复制代码

  这个函数中主要调用了findMethodInSortedMethodList函数查找方法,其代码如下所示:

ALWAYS_INLINE static method_t *
findMethodInSortedMethodList(SEL key, const method_list_t *list)
{
    if (list->isSmallList()) {
        if (CONFIG_SHARED_CACHE_RELATIVE_DIRECT_SELECTORS && objc::inSharedCache((uintptr_t)list)) {
            return findMethodInSortedMethodList(key, list, [](method_t &m) { return m.getSmallNameAsSEL(); });
        } else {
            return findMethodInSortedMethodList(key, list, [](method_t &m) { return m.getSmallNameAsSELRef(); });
        }
    } else {
        return findMethodInSortedMethodList(key, list, [](method_t &m) { return m.big().name; });
    }
}
复制代码

  在这个函数中又调用了findMethodInSortedMethodList函数,其代码如下所示:

/***********************************************************************
 * search_method_list_inline
 **********************************************************************/
template<class getNameFunc>
ALWAYS_INLINE static method_t *
findMethodInSortedMethodList(SEL key, const method_list_t *list, const getNameFunc &getName)
{
    ASSERT(list);

    auto first = list->begin();
    auto base = first;
    decltype(first) probe;

    uintptr_t keyValue = (uintptr_t)key;
    uint32_t count;
    
    for (count = list->count; count != 0; count >>= 1) {
        probe = base + (count >> 1);
        
        uintptr_t probeValue = (uintptr_t)getName(probe);
        
        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)getName((probe - 1))) {
                probe--;
            }
            return &*probe;
        }
        
        if (keyValue > probeValue) {
            base = probe + 1;
            count--;
        }
    }
    
    return nil;
}
复制代码

  这个就是二分查找算法的实现函数,假设list是有10个从小到大排序完毕的方法数组,我们要查找的sel正好为其第7个元素,那么查找流程如下图所示:

image.png

image.png

image.png

image.png

image.png

2.4 从C++又回到汇编

  二分查找中如果查找不到sel对应的imp实现,就会返回nil,回到lookupImpOrForward函数的for循环中,查找不到sel在本clsMethods_list中所对应impMethodmeth就为nil,接着就会获取curClass的超类赋值给curClass,然后再调用cache_getImp函数查找超类中缓存中是否存在sel所对应的imp实现,所有我们再来看一下cache_getImp函数的实现,如下所示:

image.png

  但是在源码中只找到了方法的声明,并没有找到实现,因此,全局搜索一下cache_getImp,结果找到了objc-msg-arm64.s文件中的汇编代码,如下所示:

	STATIC_ENTRY _cache_getImp

	GetClassFromIsa_p16 p0, 0
	CacheLookup GETIMP, _cache_getImp, LGetImpMissDynamic, LGetImpMissConstant

LGetImpMissDynamic:
	mov	p0, #0
	ret

LGetImpMissConstant:
	mov	p0, p2
	ret

	END_ENTRY _cache_getImp
复制代码

  这里的汇编代码,会获取isa中的class,然后调用了我们所熟悉的CacheLookup这个宏中的汇编代码,在我们之前快速查找流程流程的探究中,我们知道CacheLookup如果能够查找到sel所对应的imp,就会直接跳转执行这个imp,如果找不到就会执行MissLabelDynamic,很显然,在这次cache_getImp调用中,LGetImpMissDynamic就是MissLabelDynamic所代表的实际参数,执行LGetImpMissDynamic这块汇编就会返回nil

2.5 慢速查找的过程中找到了方法实现

  如果在ClassMethods_list中或父类的缓存或者父类的Methods_list中找到了imp的实现,就会执行goto done跳出循环,而在done这个代码块中,就会判断是不是通过快速查找流程找到sel所对应imp的,如果不是就会调用log_and_fill_cache函数,其代码如下所示:

/***********************************************************************
* log_and_fill_cache
* Log this method call. If the logger permits it, fill the method cache.
* cls is the method whose cache should be filled. 
* implementer is the class that owns the implementation in question.
**********************************************************************/
static void
log_and_fill_cache(Class cls, IMP imp, SEL sel, id receiver, Class implementer)
{
#if SUPPORT_MESSAGE_LOGGING
    if (slowpath(objcMsgLogEnabled && implementer)) {
        bool cacheIt = logMessageSend(implementer->isMetaClass(), 
                                      cls->nameForLogging(),
                                      implementer->nameForLogging(), 
                                      sel);
        if (!cacheIt) return;
    }
#endif
    cls->cache.insert(sel, imp, receiver);
}
复制代码

  通过分析代码我们发现,log_and_fill_cache这个函数又调用了我们很熟悉的这个函数insert函数,而inset函数就是将查找到的imp以及对应的sel存储在所在classcache中,以便下次调用这个selimp时,可以通过快速查找的方式马上调用sel所对应的实现。

2.5 快速、慢速流程查找都未找到imp

  在以上的慢速查找流程中,如果在curClass中methods_list中未找到sel所对应imp的实现,就会获取其父类,然后在其父类的cache中查找sel所对应的imp实现,就会查找其父类中的methods_list是否能找到,如果找不到,就会获取其父类的父类,然后重复以上的流程,直到遍历到继承链的末尾(为nil),如果都找不到sel所对应imp的,就会将forward_imp赋值给imp,而forward_imp就是_objc_msgForward_impcache。虽然在整个过程中都未找到sel所对应的imp,但是系统还是会给用户一次补救的机会,也就是会执行一次下面的代码:

// No implementation found. Try method resolver once.

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

  为什么会说整个if循环中的代码会执行一次呢,我们不妨来计算演练一下,behavior初始值为30011),behavior & LOOKUP_RESOLVER0010)为2,会执行if语句中的代码,然后behavior ^= LOOKUP_RESOLVERbehavior = 0001,作为参数传入resolveMethod_locked函数,在函数resolveMethod_locked的下层调用过程中还会调用lookUpImpOrForward函数,如果再次未找到sel所对应的imp,由于behavior(此时为10001& LOOKUP_RESOLVER(为20010)为0,就不会再次执行if语句中的代码了,而是直接执行_objc_msgForward_impcache中的代码,当全局搜索_objc_msgForward_impcache这个imp时,我们只能找到它在objc-msg-arm64.s文件中的汇编实现代码,如下所示:

        STATIC_ENTRY __objc_msgForward_impcache

	// No stret specialization.
	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中的汇编代码,adrp这个汇编指令的作用是将__objc_forward_handler符号所在的内存地址的低12位清零(也就是__objc_forward_handler地址所在页的基地址),然后赋值给x17寄存器,ldr这条汇编指令的意思是将p17寄存器的地址加上__objc_forward_handler符号在页中的偏移量赋值给p17(也就是__objc_forward_handler函数的入口地址),TailCallFunctionPointer宏定义如下:

.macro TailCallFunctionPointer
	// $0 = function pointer value
	braaz	$0
.endmacro
复制代码

  braaz指令的作用就是跳转到$0执行代码,而此时$0就是__objc_forward_handler函数入口的地址,所有全局搜索_objc_forward_handler,我们可以找到_objc_forward_handlerC++代码实现,如下所示:

#if !__OBJC2__

// Default forward handler (nil) goes to forward:: dispatch.
void *_objc_forward_handler = nil;
void *_objc_forward_stret_handler = nil;

#else

// 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;
复制代码

  可以发现,在OBJC2的环境下_objc_forward_handler被赋值为objc_defaultForwardHandler,而objc_defaultForwardHandler函数代码会打印当调用某个未实现的方法的报错信息unrecognized selector sent to instance,这样,我们就对这个报错有了相当深入的了解了,其实,苹果对于调用未实现的方法,也是提供了补救措施的,那就是resolveMethod_locked函数中的动态方法决议,这个内容我们下篇文章会详细探讨。

3. 慢速查找流程总结

3.1 慢速查找流程简述

  1. 从快速查找流程中的汇编代码objc_msgSend_uncachedC++函数lookupImpOrForward
  2. 递归实现Class继承链中所有的superClass以及isa指向链中所有的mataclass,初始化Class继承链中所有的superClass
  3. 判断curClass->cache.isConstantOptimizedCache(/* strict */true)是否为真,为真,执行步骤4;为假,执行步骤5
  4. 调用cache_getImp获取curClasssel所对应的impimp不为nil,执行步骤6;否则,curClass = curClass->cache.preoptFallbackClass(),执行步骤11
  5. 使用二分查找算法查找curClassMethods_list中是否有sel所对应的imp,如果查找成功,执行步骤7;未查找到,执行步骤8
  6. 判断slowpath((behavior & LOOKUP_NIL) && imp == forward_imp是否为真,为真,返回nil,执行步骤17;为假,返回imp,执行步骤15
  7. 判断是否是快速查找方式获取得到的imp,如果是,返回imp,执行步骤15;不是,执行步骤6
  8. 判断其父类是否存在,不存在,执行步骤10;存在,curClass赋值为所获取到的父类,执行步骤11
  9. 调用log_and_fill_cache函数将通过慢速查找所获取到的selimp缓存到所对应class的成员变量cachebuckets中,然后返回imp,执行步骤17
  10. imp赋值为forward_imp,然后执行步骤13
  11. 调用cache_getImp获取curClasscacheselsel对应的imp,执行步骤12.
  12. 判断imp是否是forward_imp,是,执行步骤14;否,执行步骤13
  13. 判断imp是否存在,存在执行步骤15;为假,执行步骤3.
  14. 判断slowpath(behavior & LOOKUP_RESOLVER)是否为真,为真,behavior ^= LOOKUP_RESOLVER,调用resolveMethod_locked函数(执行方法动态决议);为假,返回imp,执行步骤15
  15. 判断fastpath((behavior & LOOKUP_NOCACHE) == 0)是否为真,为真,执行步骤9;为假,执行步骤16
  16. 判断slowpath((behavior & LOOKUP_NIL) && imp == forward_imp)是否为真,为真,返回nil,执行步骤17;为假,返回imp,执行步骤17
  17. 跳转到lookupImpOrForward函数所返回的imp执行代码。

3.2 慢速查找流程图

慢速查找流程图

文章分类
iOS
文章标签