美文网首页
OC方法查找(消息发送)流程

OC方法查找(消息发送)流程

作者: 希尔罗斯沃德_董 | 来源:发表于2021-08-03 16:29 被阅读0次

    认识runtime

    参考官方文档

    objc_msgSend

    OC方法其本质上就是通过调用objc_msgSend函数来发送消息,objc_msgSend函数的声明如下:

    id objc_msgSend(id self, SEL _cmd, ...);
    

    objc_msgSend函数有有两个重要的参数self和_cmd,self就是消息接收者,_cmd就是方法,也就是消息。objc_msgSend函数里面会通过self的isa指针找到对应的类,从类结构中查询方法实现进行执行。这里附上一张isa指针流程图以及详细分析文章链接(点击了解isa及其详细走位流程):

    isa流程图.png

    快速查找(缓存)

    objc_msgSend代码是由汇编实现的,其汇编代码如下:

        ENTRY _objc_msgSend //进入_objc_msgSend
        UNWIND _objc_msgSend, NoFrame
    
        cmp p0, #0          // nil check and tagged pointer check, p0=self
    #if SUPPORT_TAGGED_POINTERS
        b.le    LNilOrTagged        //  (MSB tagged pointer looks negative)
    #else
        b.eq    LReturnZero
    #endif
        ldr p13, [x0]       // p13 = isa
        GetClassFromIsa_p16 p13     // p16 = class
    LGetIsaDone:
        // calls imp or objc_msgSend_uncached
        CacheLookup NORMAL, _objc_msgSend
    
    #if SUPPORT_TAGGED_POINTERS
    ......这里省略 TAGGED_POINTERS相关的流程,因为当前不做分析
    // SUPPORT_TAGGED_POINTERS
    #endif
    
    LReturnZero:
        // x0 is already zero
        mov x1, #0
        movi    d0, #0
        movi    d1, #0
        movi    d2, #0
        movi    d3, #0
        ret
    
        END_ENTRY _objc_msgSend
    

    判断不是targed_pointer类型后,调用宏定义函数GetClassFromIsa_p16通过将isa指针进行一系列指针平移,最后和掩码进行与操作得到Class指针:

    .macro GetClassFromIsa_p16 /* src */
    
    #if SUPPORT_INDEXED_ISA
        // Indexed isa
        mov p16, $0         // optimistically set dst = src
        tbz p16, #ISA_INDEX_IS_NPI_BIT, 1f  // done if not non-pointer isa
        // isa in p16 is indexed
        adrp    x10, _objc_indexed_classes@PAGE
        add x10, x10, _objc_indexed_classes@PAGEOFF
        ubfx    p16, p16, #ISA_INDEX_SHIFT, #ISA_INDEX_BITS  // extract index
        ldr p16, [x10, p16, UXTP #PTRSHIFT] // load class from array
    1:
    
    #elif __LP64__
        // 64-bit packed isa
        and p16, $0, #ISA_MASK //与 ISA_MASK进行&操作读取Class指针
    
    #else
        // 32-bit raw isa
        mov p16, $0
    
    #endif
    
    .endmacro
    

    拿到Class之后,接着就通过函数CacheLookup缓存中进行查找:

    .macro CacheLookup
        //
        // Restart protocol:
        //
        //   As soon as we're past the LLookupStart$1 label we may have loaded
        //   an invalid cache pointer or mask.
        //
        //   When task_restartable_ranges_synchronize() is called,
        //   (or when a signal hits us) before we're past LLookupEnd$1,
        //   then our PC will be reset to LLookupRecover$1 which forcefully
        //   jumps to the cache-miss codepath which have the following
        //   requirements:
        //
        //   GETIMP:
        //     The cache-miss is just returning NULL (setting x0 to 0)
        //
        //   NORMAL and LOOKUP:
        //   - x0 contains the receiver
        //   - x1 contains the selector
        //   - x16 contains the isa
        //   - other registers are set as per calling conventions
        //
    LLookupStart$1:
    
        // p1 = SEL, p16 = isa
        ldr p11, [x16, #CACHE]              // p11 = mask|buckets,找到缓存
    
    #if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16
        and p10, p11, #0x0000ffffffffffff   // p10 = buckets
        and p12, p1, p11, LSR #48       // x12 = _cmd & mask
    #elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_LOW_4
        and p10, p11, #~0xf         // p10 = buckets
        and p11, p11, #0xf          // p11 = maskShift
        mov p12, #0xffff
        lsr p11, p12, p11               // p11 = mask = 0xffff >> p11
        and p12, p1, p11                // x12 = _cmd & mask
    #else
    #error Unsupported cache mask storage for ARM64.
    #endif
    
    
        add p12, p10, p12, LSL #(1+PTRSHIFT)
                         // p12 = buckets + ((_cmd & mask) << (1+PTRSHIFT))
    
        ldp p17, p9, [x12]      // {imp, sel} = *bucket
    1:  cmp p9, p1          // if (bucket->sel != _cmd)
        b.ne    2f          //     scan more
        CacheHit $0         // call or return imp
        
    2:  // not hit: p12 = not-hit bucket
        CheckMiss $0            // miss if bucket->sel == 0
        cmp p12, p10        // wrap if bucket == buckets
        b.eq    3f
        ldp p17, p9, [x12, #-BUCKET_SIZE]!  // {imp, sel} = *--bucket
        b   1b          // loop
    
    3:  // wrap: p12 = first bucket, w11 = mask
    #if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16
        add p12, p12, p11, LSR #(48 - (1+PTRSHIFT))
                        // p12 = buckets + (mask << 1+PTRSHIFT)
    #elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_LOW_4
        add p12, p12, p11, LSL #(1+PTRSHIFT)
                        // p12 = buckets + (mask << 1+PTRSHIFT)
    #else
    #error Unsupported cache mask storage for ARM64.
    #endif
    
        // Clone scanning loop to miss instead of hang when cache is corrupt.
        // The slow path may detect any corruption and halt later.
    
        ldp p17, p9, [x12]      // {imp, sel} = *bucket
    1:  cmp p9, p1          // if (bucket->sel != _cmd)
        b.ne    2f          //     scan more
        CacheHit $0         // call or return imp
        
    2:  // not hit: p12 = not-hit bucket
        CheckMiss $0            // miss if bucket->sel == 0
        cmp p12, p10        // wrap if bucket == buckets
        b.eq    3f
        ldp p17, p9, [x12, #-BUCKET_SIZE]!  // {imp, sel} = *--bucket
        b   1b          // loop
    
    LLookupEnd$1:
    LLookupRecover$1:
    3:  // double wrap
        JumpMiss $0
    
    .endmacro
    

    其大概逻辑是通过指针平移找到类的cache_t cache(点击了解cache_t),然后再利用bucketsMask和_maskAndBuckets通过位运算得到相应的buckets指针,然后再通过SEL _cmd和对应的mask做位运算得到一个index,先通过index在buckets中读取出sel,如果跟当前的_cmd一致那就返回,如果不一致就跳到buckets的末尾,通过指针平移从后往前查找(可能是因为存的时候大方向是从前往后的,当然不一定是按顺序存储),找到(CacheHit)返回imp。找不到(CheckMiss)就调用函数__objc_msgSend_uncached:

        STATIC_ENTRY __objc_msgSend_uncached
        UNWIND __objc_msgSend_uncached, FrameWithNoSaves
    
        // THIS IS NOT A CALLABLE C FUNCTION
        // Out-of-band p16 is the class to search
        
        MethodTableLookup
        TailCallFunctionPointer x17
    
        END_ENTRY __objc_msgSend_uncached
    

    函数__objc_msgSend_uncached的实现实际上就调用了MethodTableLookup函数:

    .macro MethodTableLookup
        
        // push frame
        SignLR
        stp fp, lr, [sp, #-16]!
        mov fp, sp
    
        // save parameter registers: x0..x8, q0..q7
        sub sp, sp, #(10*8 + 8*16)
        stp q0, q1, [sp, #(0*16)]
        stp q2, q3, [sp, #(2*16)]
        stp q4, q5, [sp, #(4*16)]
        stp q6, q7, [sp, #(6*16)]
        stp x0, x1, [sp, #(8*16+0*8)]
        stp x2, x3, [sp, #(8*16+2*8)]
        stp x4, x5, [sp, #(8*16+4*8)]
        stp x6, x7, [sp, #(8*16+6*8)]
        str x8,     [sp, #(8*16+8*8)]
    
        // 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 registers and return
        ldp q0, q1, [sp, #(0*16)]
        ldp q2, q3, [sp, #(2*16)]
        ldp q4, q5, [sp, #(4*16)]
        ldp q6, q7, [sp, #(6*16)]
        ldp x0, x1, [sp, #(8*16+0*8)]
        ldp x2, x3, [sp, #(8*16+2*8)]
        ldp x4, x5, [sp, #(8*16+4*8)]
        ldp x6, x7, [sp, #(8*16+6*8)]
        ldr x8,     [sp, #(8*16+8*8)]
    
        mov sp, fp
        ldp fp, lr, [sp], #16
        AuthenticateLR
    
    .endmacro
    

    MethodTableLookup函数中一大堆实现也不知道干嘛,但是有一个很重要的信息就是调用了lookUpImpOrForward函数,这其实就是进入了方法慢速查找流程。

    慢速方法查找流程

    当缓存中找不到方法时就会进入慢速查找过程,实际上就是调用lookUpImpOrForward函数,其代码如下:

    /***********************************************************************
    * lookUpImpOrForward.
    * The standard IMP lookup. 
    * Without LOOKUP_INITIALIZE: tries to avoid +initialize (but sometimes fails)
    * Without LOOKUP_CACHE: skips optimistic unlocked lookup (but uses cache elsewhere)
    * Most callers should use LOOKUP_INITIALIZE and LOOKUP_CACHE
    * inst is an instance of cls or a subclass thereof, or nil if none is known. 
    *   If cls is an un-initialized metaclass then a non-nil inst is faster.
    * May return _objc_msgForward_impcache. IMPs destined for external use 
    *   must be converted to _objc_msgForward or _objc_msgForward_stret.
    *   If you don't want forwarding at all, use LOOKUP_NIL.
    **********************************************************************/
    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;
    }
    

    函数进来之后再次执行快速查找流程,这一步主要考虑到多线程的原因有可能在这段时间前有方法被缓存了,找到就返回,找不到就继续往下。这期间还有各种校验,包括类是否已初始化等校验。然后才进行如下查找流程:

    1、通过当前类结构体查找方法列表:

    首先通过调用getMethodNoSuper_nolock从当前类的方法列表中查找:

    Method meth = getMethodNoSuper_nolock(curClass, sel);
            if (meth) {
                imp = meth->imp;
                goto done;
            }
    
    

    函数getMethodNoSuper_nolock通过当前类获取方法列表 cls->data()->methods(),然后交由函数search_method_list_inline通过调用函数findMethodInSortedMethodList进行二分法查找

    /***********************************************************************
     * 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;
    }
    
    ALWAYS_INLINE static method_t *
    search_method_list_inline(const method_list_t *mlist, SEL sel)
    {
        int methodListIsFixedUp = mlist->isFixedUp();
        int methodListHasExpectedSize = mlist->entsize() == sizeof(method_t);
        
        if (fastpath(methodListIsFixedUp && methodListHasExpectedSize)) {
            return findMethodInSortedMethodList(sel, mlist);
        } else {
            // Linear search of unsorted method list
            for (auto& meth : *mlist) {
                if (meth.name == sel) return &meth;
            }
        }
    
    #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中进行二分法查找。因为方法列表首先是个数组,而且是个有序数组,它是根据method_t的SEL的地址大小进行排序的,所以可以进行二分法查找:

    /***********************************************************************
     * search_method_list_inline
     **********************************************************************/
    ALWAYS_INLINE static method_t *
    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);//右移一位实际上就是除以2,这一语句相当于指针往前走到中间位置,这里是二分法
            
            uintptr_t probeValue = (uintptr_t)probe->name;
            
            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--;//这里减减操作是考虑多个SEL同名,优先调用最前面的一个,比如category方法,运行时会添加到函数列表前面,因此会被优先查询到
                }
                return (method_t *)probe;
            }
            
            if (keyValue > probeValue) {
                base = probe + 1;
                count--;
            }
        }
        
        return nil;
    }
    

    如果找到就会返回imp,并且会把sel和imp都缓存到cache里面。

    2、找到并写入缓存:
     done:
        log_and_fill_cache(cls, imp, sel, inst, curClass);
        runtimeLock.unlock();
    
    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
        cache_fill(cls, sel, imp, receiver);//在这方法里面里会调用insert方法进行写入缓存
    }
    

    最后会在方法cache_fill(cls, sel, imp, receiver)里面里会调用insert方法进行写入缓存(点击了解详情);
    如果找不到就往下对父类进行递归查找。

    3、递归地从父类查找

    首先通过curClass = curClass->superclass获取当前类的父类,然后赋值给curClass,之后就会先获取父类缓存,找到就返回,找不到继续进行for循环寻找父类方法列表:

        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;
            }
        }
    

    如果父类找不到,基类也找不到,也就是superclass=nil时,这时候imp会被赋予一个默认值forward_imp,这个forward_imp在函数开始时就有赋值了:

    4、继承链中找不到方法时
    const IMP forward_imp = (IMP)_objc_msgForward_impcache;
    

    那这个_objc_msgForward_impcache到底是什么东西呢?经过跟踪发现它是由汇编实现的:

        STATIC_ENTRY __objc_msgForward_impcache
    
        // No stret specialization.
        b   __objc_msgForward //调用函数__objc_msgForward
    
        END_ENTRY __objc_msgForward_impcache
    
        
        ENTRY __objc_msgForward   //开始调用__objc_msgForward
    
        adrp    x17, __objc_forward_handler@PAGE
        ldr p17, [x17, __objc_forward_handler@PAGEOFF]
        TailCallFunctionPointer x17 //调用函数__objc_forward_handler
        
        END_ENTRY __objc_msgForward  //结束调用__objc_msgForward
    

    根据代码分析,其最终调用的是函数_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 sent to instance......”的地方。
    当然,到基类这一步方法如果还没有找到,它其实不会立即执行_objc_forward_handler这个函数。苹果还给我们提供了补救的方案。那就是接下来的动态方法决议和消息转发流程。

    5、动态方法决议

    递归到基类再找不到就会跳出for循环,并进入动态方法决议:

     // No implementation found. Try method resolver once.
        if (slowpath(behavior & LOOKUP_RESOLVER)) {
            behavior ^= LOOKUP_RESOLVER;
            return resolveMethod_locked(inst, sel, cls, behavior);
        }
    

    从resolveMethod_locked函数的实现可以看出,动态方法决议中对象方法和类方法的处理流程是不一样的:

    static NEVER_INLINE IMP
    resolveMethod_locked(id inst, SEL sel, Class cls, int behavior)
    {
        runtimeLock.assertLocked();
        ASSERT(cls->isRealized());
    
        runtimeLock.unlock();
    
        if (! cls->isMetaClass()) { //cls不是元类,说不是类方法,是对象方法 
            // 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);
    }
    

    动态决议执行完成之后又会调用一遍lookUpImpOrForward。接下来看一下对象方法动态决议过程。

    • 对象方法动态决议
      当判断是对象方法时,会直接调用resolveInstanceMethod函数:
    /***********************************************************************
    * resolveInstanceMethod
    * Call +resolveInstanceMethod, looking for a method to be added to class cls.
    * cls may be a metaclass or a non-meta class.
    * Does not check if the method already exists.
    **********************************************************************/
    static void resolveInstanceMethod(id inst, SEL sel, Class cls)
    {
        runtimeLock.assertUnlocked();
        ASSERT(cls->isRealized());
        SEL resolve_sel = @selector(resolveInstanceMethod:);
    
        if (!lookUpImpOrNil(cls, resolve_sel, cls->ISA())) {
            // Resolver not implemented.
            return;
        }
    
        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 = 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 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));
            }
        }
    }
    

    这时就会首先判断我的cls有没有实现方法+ (BOOL)resolveInstanceMethod:(SEL)sel,如果没有实现就直接return。如果实现了,那么就会通过objc_msgSend像cls发送resolve_sel消息,实际上就是调用resolveInstanceMethod方法,调用这个方法同样也会走一遍消息发送流程,如果这个cls或者其父类实现resolveInstanceMethod方法,并对sel进行处理,比如当我们调用一个没有实现的方法noMethod时,可以再resolveInstanceMethod中进行拦截,并添加一个新的方法,如下:

    - (void)instanceMethod{
        NSLog(@"这是实例方法");
    }
    
    + (BOOL)resolveInstanceMethod:(SEL)sel{
        
        if (sel == @selector(noMethod)) {
            IMP imp           = class_getMethodImplementation(self, @selector(instanceMethod));
            Method aMethod = class_getInstanceMethod(self, @selector(instanceMethod));
            const char *type  = method_getTypeEncoding(aMethod);
            return class_addMethod(self, sel, imp, type);
        }
        
        return [super resolveInstanceMethod:sel];
    }
    

    这样当函数static void resolveInstanceMethod(id inst, SEL sel, Class cls)中再次调用 IMP imp = lookUpImpOrNil(inst, sel, cls) 时就能找到并调用IPM,这个IPM就是instanceMethod。

    • 类方法动态决议
      如果是类方法,则调用方法resolveClassMethod,如下:
    /***********************************************************************
    * resolveClassMethod
    * Call +resolveClassMethod, looking for a method to be added to class cls.
    * cls should be a metaclass.
    * Does not check if the method already exists.
    **********************************************************************/
    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));
            }
        }
    }
    

    类方法其实跟对象方法很相似,就是调用resolveClassMethod换成resolveInstanceMethod,处理流程是差不多的。这里就不进行分析。主要不同的是,类方法调用完resolveClassMethod之后,还会进行一次lookUpImpOrNil,并在此调用resolveInstanceMethod,这又是为什么呢?

     resolveClassMethod(inst, sel, cls);
            if (!lookUpImpOrNil(inst, sel, cls)) {
                resolveInstanceMethod(inst, sel, cls);
            }
    

    实际上这跟isa的走位有关(点击了解isa走位流程),虽然代码的外部结构我看到的是类方法,但实际上类方法本身它也是元类的对象方法,方法存储在元类中,而元类的基类又是NSObject,因此还需要调用一次对象方法动态决议。举个例子,加入我们在NSObject的category里面实现了对象方法决议,如下demo:

    @implementation NSObject (Test)
    
    - (void)instanceMethod{
        NSLog(@"这是实例方法");
    }
    
    + (BOOL)resolveInstanceMethod:(SEL)sel{
        
        if (sel == @selector(noMethod)) {
            IMP imp           = class_getMethodImplementation(self, @selector(instanceMethod));
            Method aMethod = class_getInstanceMethod(self, @selector(instanceMethod));
            const char *type  = method_getTypeEncoding(aMethod);
            return class_addMethod(self, sel, imp, type);
        }
        
        return NO;
    }
    
    @end
    

    然后我们从NSObject的一个子类MyObject调用一个不存在的类方法:

        [MyObject performSelector:@selector(noMethod)];
    

    最终也会调用到NSObject的resolveInstanceMethod方法里面,接受处理。
    动态方法决议只是给我们提供了一个补救的方式处理未实现的方法调用(当然实际开发中一般不在NSObject这做处理,影响比较大)。但是如果我们没有实现动态决议相关的处理的话,那么消息发送路程就会进入消息转发流程

    6、消息转发流程

    消息转发流程包括两个步骤,快速转发和慢速转发。

    • 快速转发
      进入快速转发流程实际上是调用了forwardingTargetForSelector方法,通过这个方法返回一个指定的对象负责处理这个消息,举一个例子:
    @interface MyObject1 : NSObject
    
    @end
    
    @implementation MyObject1
    
    - (void)noMethod
    {
        NSLog(@"这是MyObject1的方法");
    }
    
    @end
    
    
    @interface MyObject : NSObject
    - (void)instanceMethod;
    + (void)classMethod;
    @end
    
    @implementation MyObject
    
    - (id)forwardingTargetForSelector:(SEL)aSelector
    {
        if (aSelector == @selector(noMethod)) {
            return [[MyObject1 alloc] init];
        }
        return nil;
    }
    }
    

    demo中MyObject实现了forwardingTargetForSelector方法,并且判断如果是调用noMethod方法的时候就转发给MyObject1的对象去处理。比如进行如下调用:

     MyObject *objc = [[MyObject alloc] init];
        [objc performSelector:@selector(noMethod)];
    

    那么这段代码最终会执行MyObject1的noMethod方法。因为这个转发直接指定一个接收转发消息的对象,所以叫快速转发。注意,这一段转发之后,MyObject1的对象在调用noMethod方法时也是走一遍消息发送流程的。如果在这一层没有处理的话,那么消息发送就会进入慢速转发流程。

    • 慢速转发
      慢速转发流程会涉及到两个方法。如果我们想在这一层处理这一消息需要实现两个方法forwardInvocation和methodSignatureForSelector。首先我们需要在methodSignatureForSelector中创建一个方法签名,例如上面demo中的noMethod方法可以这样处理:
    - (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
    {
        NSMethodSignature *methodSignature = [NSMethodSignature signatureWithObjCTypes:"v@:"];
        return methodSignature;
    }
    
    

    创建这个签名methodSignature并返回之后会继续调用forwardInvocation(实际上在调用forwardInvocation之前还做了一次动态方法决议),这里面涉及到一个很重要的类NSInvocation,NSInvocation的定义如下:

    @interface NSInvocation : NSObject
    
    + (NSInvocation *)invocationWithMethodSignature:(NSMethodSignature *)sig;
    
    @property (readonly, retain) NSMethodSignature *methodSignature;
    
    - (void)retainArguments;
    @property (readonly) BOOL argumentsRetained;
    
    @property (nullable, assign) id target;
    @property SEL selector;
    
    - (void)getReturnValue:(void *)retLoc;
    - (void)setReturnValue:(void *)retLoc;
    
    - (void)getArgument:(void *)argumentLocation atIndex:(NSInteger)idx;
    - (void)setArgument:(void *)argumentLocation atIndex:(NSInteger)idx;
    
    - (void)invoke;
    - (void)invokeWithTarget:(id)target;
    
    @end
    

    其属性methodSignature就是保存了我们刚才创建的签名,还有两个重要属性target和selector,selector就是调用的noMethod方法,target就是调用这个方法的对象。NSInvocation中还有两个方法invoke和invokeWithTarget,执行invoke就是让当前target调用selector,而invokeWithTarget则可以指定新的target调用selector,比如调用noMethod我们可以这样处理:

    - (void)forwardInvocation:(NSInvocation *)invocation
    {
        [invocation invokeWithTarget:[[MyObject1 alloc] init]];
    }
    
    

    这里我们创建MyObject1的对象并指定为noMethod的调用者。注意:在这一步虽然是通过invoke调用,但是实际上最后也是向target对象发送消息,依然会走消息发送的流程。
    其实到我们实现了- (void)forwardInvocation:(NSInvocation *)invocation这个方法的时候,只要methodSignatureForSelector返回不为nil,程序不会因此而崩溃,如果methodSignatureForSelector返回为nil将不会再调用forwardInvocation方法而直接调用前文提到的默认imp,然后调用方法doesNotRecognizeSelector,最后程序崩溃。
    慢速转发的优点
    在forwardInvocation方法中我们可以选择做很多事情,相比于快速转发的方法forwardingTargetForSelector有很一些优点:

    1、不止可以指定新的target,还可以修改selector;
    2、invokation对象可以缓存,在合适的时机调用(invoke);

    总结

    1、通过isa指针获取当前类结构,在类结构缓存中进行快速查找,命中缓存则直接返回,没有命中执行2步骤;
    2、通过当前类结构寻找方法列表,在方法列表中查找,找不到执行3步骤,找到就执行7步骤;
    3、递归寻找父类的缓存和方法列表(实际是调用步骤1),找不到就就会设置一个默认的执行函数,但不返回,先执行步骤4,如果找到就执行步骤7;
    4、进入方法决议,方法决议没有处理就执行步骤4;
    5、就进入消息转发流程,消息转发流程如果没有处理就会执行步骤6 。
    6、调用默认函数,程序崩溃并打印方法无法找到的崩溃信息
    7、找到方法就返回,同时写入缓存。

    相关文章

      网友评论

          本文标题:OC方法查找(消息发送)流程

          本文链接:https://www.haomeiwen.com/subject/hidrvltx.html