美文网首页
iOS - objc_msgSend()详解

iOS - objc_msgSend()详解

作者: felix6 | 来源:发表于2018-05-21 09:40 被阅读0次

    [toc]

    参考

    objc_msgSend() 详解

    objc4

    http://www.jianshu.com/p/1bde36ad9938

    objc_msgSend() 简介

    方法调用本质

    OC中的方法调用, 其实都是转换为 objc_msgSend() 函数的调用;

    消息机制: 给方法调用者发送消息

    Person *person = [[Person alloc] init];
    // objc_msgSend(person, @selector(personTest));
    // 消息接收者(receiver):person
    // 消息名称:personTest
    [person personTest];
    
    // objc_msgSend([Person class], @selector(initialize));
    // 消息接收者(receiver):[Person class]
    // 消息名称:initialize
    [Person initialize];
    
    流程

    objc_msgSend 的执行流程可以分为3大阶段

    • 消息发送

    • 动态方法解析

    • 消息转发

    补救方案

    首先, 调用方法时, 系统会查看这个对象能否接收这个消息 (查看这个类有没有这个方法, 或者有没有实现这个方法。)

    如果不能, 就会调用下面这几个方法, 给你“补救”的机会, 你可以先理解为几套防止程序crash的备选方案, 我们就是利用这几个方案进行消息转发;

    注意, 前一套方案实现, 后一套方法就不会执行。

    如果这几套方案你都没有做处理, 那么 objc_msgSend() 最后找不到合适的方法进行调用, 会报错 unrecognized selector sent to instance

    方案1 (动态方法解析, 添加方法):

    // 首先,系统会调用resolveInstanceMethod或resolveClassMethod 让你自己为这个方法动态增加实现。动态添加方法
    + (BOOL)resolveInstanceMethod:(SEL)sel;
    + (BOOL)resolveClassMethod:(SEL)sel;
    

    方案2 (消息转发):

    // 如果不对resolveInstanceMethod做任何处理, 系统会来到方案二, 走forwardingTargetForSelector方法,我们可以返回其他实例对象, 实现消息转发。
    - (id)forwardingTargetForSelector:(SEL)aSelector;
    

    方案3 (消息转发调用):

    // 如果不实现 forwardingTargetForSelector, 系统就会调用方案三的两个方法 方法签名&转发调用
    - (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector;
    - (void)forwardInvocation:(NSInvocation *)anInvocation;
    
    流程图示
    image

    消息发送 😳

    查找方法的执行流程

    图示

    image

    源码解读

    objc_msgSend()

    因为这个方法调用频次太高, 所以使用汇编实现提高效率

    // objc-msg-arm64.s
    
        ENTRY _objc_msgSend
        UNWIND _objc_msgSend, NoFrame
        // p0寄存器, 存放的是消息接收者 receiver, 判断是否为0
        cmp p0, #0          // nil check and tagged pointer check
    #if SUPPORT_TAGGED_POINTERS 
        // b是跳转; 如果 le (p0≤0)成立, 则跳转到 LNilOrTagged
        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
    LNilOrTagged:
        b.eq    LReturnZero     // nil check
    
        // tagged
        adrp    x10, _objc_debug_taggedpointer_classes@PAGE
        add x10, x10, _objc_debug_taggedpointer_classes@PAGEOFF
        ubfx    x11, x0, #60, #4
        ldr x16, [x10, x11, LSL #3]
        adrp    x10, _OBJC_CLASS_$___NSUnrecognizedTaggedPointer@PAGE
        add x10, x10, _OBJC_CLASS_$___NSUnrecognizedTaggedPointer@PAGEOFF
        cmp x10, x16
        b.ne    LGetIsaDone
    
        // ext tagged
        adrp    x10, _objc_debug_taggedpointer_ext_classes@PAGE
        add x10, x10, _objc_debug_taggedpointer_ext_classes@PAGEOFF
        ubfx    x11, x0, #52, #8
        ldr x16, [x10, x11, LSL #3]
        b   LGetIsaDone
    // 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
    
    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
    
    CheckMiss

    缓存没有命中

    .macro CheckMiss
        // miss if bucket->sel == 0
    .if $0 == GETIMP
        cbz p9, LGetImpMiss
    .elseif $0 == NORMAL // _objc_msgSend 调用了 CacheLookup NORMAL ★
        cbz p9, __objc_msgSend_uncached
    .elseif $0 == LOOKUP
        cbz p9, __objc_msgLookup_uncached
    .else
    .abort oops
    .endif
    .endmacro
    
    .macro JumpMiss
    .if $0 == GETIMP
        b   LGetImpMiss
    .elseif $0 == NORMAL
        b   __objc_msgSend_uncached
    .elseif $0 == LOOKUP
        b   __objc_msgLookup_uncached
    .else
    .abort oops
    .endif
    .endmacro
    
    
    __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_msgLookup_uncached
    STATIC_ENTRY __objc_msgLookup_uncached
    UNWIND __objc_msgLookup_uncached, FrameWithNoSaves
    
    // THIS IS NOT A CALLABLE C FUNCTION
    // Out-of-band p16 is the class to search
    
    MethodTableLookup
    ret
    
    END_ENTRY __objc_msgLookup_uncached
    
    _cache_getImp
        STATIC_ENTRY _cache_getImp
    
        GetClassFromIsa_p16 p0
        CacheLookup GETIMP, _cache_getImp
    
    LGetImpMiss:
        mov p0, #0
        ret
    
        END_ENTRY _cache_getImp
    
    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
        // 汇编里面 b 开头的指令一般是跳转调用
        // c函数在编译成汇编之后, 会多一条下划线, 找到c的 lookUpImpOrForward, 返回的是函数地址 IMP ★
        // 传入的参数 behavior = LOOKUP_INITIALIZE | LOOKUP_RESOLVER  = 0b0001 | 0b0010 = 0b0011
        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
    
    lookUpImpOrForward()

    MethodTableLookup 和 resolveMethod_locked() 调用

    // objc-runtime-new.mm
    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 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().
        // 如果当前类的缓存中没有, 则遍历, 根据superclass指针向上一层层查找 ★★
        for (unsigned attempts = unreasonableClassCount();;) {
            
            // 根据方法名 sel 去当前`游标`中查找方法 ★ 
            // curClass method list.
            Method meth = getMethodNoSuper_nolock(curClass, sel); // ★
            if (meth) {
                // 取出函数地址 IMP
                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.
        // 首次查找时, 没找到, 尝试一次动态方法解析 ★★★
        // 此时, behavior = 0b0011; &= LOOKUP_RESOLVER 为 0b0010, 可以进入if(); 
        
        // 由于 resolveMethod_locked 内部调用了本函数, 而如果 resolveMethod_locked 并没有动态添加方法, 会再次来到这里
        // 此时, behavior = 0b0101; &= LOOKUP_RESOLVER 为 0b0000, 不能进入if();
        if (slowpath(behavior & LOOKUP_RESOLVER)) {
            // behavior = 0b0011 ^ 0b0010 = 0b0001
            behavior ^= LOOKUP_RESOLVER;
            // 该函数内部会回调本函数, behavior = behavior | LOOKUP_CACHE = 0b0101  ★
            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;
    }
    
    
    enum {
        LOOKUP_INITIALIZE = 1, // 0b0001
        LOOKUP_RESOLVER = 2, // 0b0010
        LOOKUP_CACHE = 4, // 0b0100
        LOOKUP_NIL = 8, // 0b1000
    };
    
    getMethodNoSuper_nolock()

    根据方法名 sel 去 类/元类 中查找方法

    static method_t *getMethodNoSuper_nolock(Class cls, SEL sel) {
        runtimeLock.assertLocked();
    
        ASSERT(cls->isRealized());
        // fixme nil cls? 
        // fixme nil sel?
        
        // cls->data() 即 bits & FAST_DATA_MASK 可以获取到 struct class_rw_t, 然后从中拿到 methods (二维数组) ★
        auto const methods = cls->data()->methods();
        // 遍历二维数组, 找到 method_t
        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;
    }
    
    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->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()
    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);
            
            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--;
                }
                return (method_t *)probe;
            }
            
            if (keyValue > probeValue) {
                base = probe + 1;
                count--;
            }
        }
        
        return nil;
    }
    
    log_and_fill_cache()

    填充缓存 - 在 lookUpImpOrForward 中被调用

    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); // ★
    }
    
    cache_fill()

    填充缓存

    void cache_fill(Class cls, SEL sel, IMP imp, id receiver) {
        runtimeLock.assertLocked();
    
    #if !DEBUG_TASK_THREADS
        // Never cache before +initialize is done
        if (cls->isInitialized()) {
            cache_t *cache = getCache(cls);
    #if CONFIG_USE_CACHE_LOCK
            mutex_locker_t lock(cacheUpdateLock);
    #endif
            // 调用 cache_t::insert 将新调用的方法插入到当前类对象的方法缓存中 ★
            cache->insert(cls, sel, imp, receiver);
        }
    #else
        _collecting_in_critical();
    #endif
    }
    

    动态方法解析 😳

    图示

    image

    源码解读

    resolveMethod_locked()

    在 lookUpImpOrForward() 中有调用

    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);
            }
        }
        
        // 再次调用 `lookUpImpOrForward()` 入参 behavior = 0b0001 | 0b0100 = 0b0101
        // chances are that calling the resolver have populated the cache
        // so attempt using it
        return lookUpImpOrForward(inst, sel, cls, behavior | LOOKUP_CACHE);
    }
    
    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;
        }
        // 给传入的 cls 发送 `resolveInstanceMethod:` ★ 
        BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
        
        // 这个返回值只是做了一些打印, 所以 `resolveInstanceMethod:` 的返回值为 YES / NO 实际效果都一样
        // `resolveInstanceMethod:` 不添加方法实现, 仅仅返回 YES 是没用的 ★
        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));
            }
        }
    }
    
    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);
            }
        }
        // 让元类调用 `resolveClassMethod:`
        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;
    
    + (BOOL)resolveClassMethod:(SEL)sel;
    
    示例
    // c函数名就是函数地址
    void c_other(id self, SEL _cmd) {
        NSLog(@"c_other - %@ - %@", self, NSStringFromSelector(_cmd));
    }
    
    + (BOOL)resolveInstanceMethod:(SEL)sel {
        if (sel == @selector(test)) {
            // 动态添加test方法的实现, 将方法添加到 class_rw_t 的 methods 中 ★
            class_addMethod(self, sel, (IMP)c_other, "v16@0:8");
            return YES;
        }
        return [super resolveInstanceMethod:sel];
    }
    
    + (BOOL)resolveClassMethod:(SEL)sel {
        if (sel == @selector(test)) {
            // 第一个参数是object_getClass(self) 元类对象
            class_addMethod(object_getClass(self), sel, (IMP)c_other, "v16@0:8");
            return YES;
        }
        return [super resolveClassMethod:sel];
    }
    

    动态解析过后, 会重新走“消息发送”的流程

    <u>“从receiverClass 的 cache中查找方法”这一步开始执行</u>

    消息转发 😳

    自己及父类都无法处理该消息, 也没有动态方法解析, 会进入消息转发阶段, 将消息转发给其他实例 /类 (备用接收者)

    注意, 消息机制支持, 类方法, 实例方法和类方法本质没有区别, 都是消息机制。

    当对象不能接受某个selector时, 如果不对 resolveInstanceMethod 做任何处理, 系统会来到 forwardingTargetForSelector 方法, 我们可以返回其他实例对象, 实现消息转发。

    图示

    image

    使用

    forwardingTargetForSelector
    // 对于类方法的转发
    + (id)forwardingTargetForSelector:(SEL)aSelector {
        
        // 这里甚至可以返回实例对象, 转发给实例对象来调用, 相当于:
        // objc_msgSend([[Cat alloc] init], @selector(test))
        // [[[Cat alloc] init] test]
        if (aSelector == @selector(test)) return [[Cat alloc] init];
    
        return [super forwardingTargetForSelector:aSelector];
    }
    
    - (id)forwardingTargetForSelector:(SEL)aSelector {
        if (aSelector == @selector(test)) {
            // 返回一个实现了该方法的对象, 实际相当于调用了下面的方法
            // `objc_msgSend([[Animal alloc] init], aSelector)`
            return [[Animal alloc] init];
        }
        return [super forwardingTargetForSelector:aSelector];
    }
    // 若返回的对象如果没有实现该方法, 相当于返回了nil 
    
    • 若实现了该方法, 且返回值不空, 则将消息转发给其他对象

    • 若未实现该方法, 或者返回了nil, 会调用 methodSignatureForSelector:, 要求返回方法签名

    methodSignatureForSelector
    // 方法签名:返回值类型、参数类型
    - (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
        if (aSelector == @selector(test)) {
            // 手写方法签名
            return [NSMethodSignature signatureWithObjCTypes:"v16@0:8"];
            // 也可以让一个实现了该方法的对象, 调用本方法, 并返回结果
            // return [[[Xxx alloc] init] methodSignatureForSelector:aSelector];
        }
        return [super methodSignatureForSelector:aSelector];
    }
    
    • 若实现了该方法, 且返回值不空, 则将其返回的方法签名封装到 NSInvocation,并调用 forwardInvocation: ;

      其中, 方法签名决定了 NSInvocation 方法的返回值及参数数量、类型。

    • 若未实现该方法, 或者返回了nil, 会调用 doesNotRecognizeSelector:, 直接报找不到方法; ★

    forwardInvocation
    /*
    * NSInvocation 封装了一个方法调用, 包括: 方法调用者、方法名、方法参数
    * anInvocation.target 方法调用者, 默认是最开始接收消息的对象
    * anInvocation.selector 方法名
    * [anInvocation getArgument:NULL atIndex:0] 调用时传入的参数
    */
    - (void)forwardInvocation:(NSInvocation *)anInvocation {
    //    anInvocation.target = [[Cat alloc] init];
    //    [anInvocation invoke];
     
        [anInvocation invokeWithTarget:[[Cat alloc] init]];
    }
    
    • 该方法内, 不是必须调用 invoke,

    • 实际上这个方法内部是可以做任何事, 甚至不做任何事;
      比如可以修改参数, 修改返回值等等;

    NSMethodSignature 顾名思义应该就是“方法签名”, 类似于C++中的编译器时的函数签名。苹果官方定义该类为对方法的参数、返回类似进行封装, 协同NSInvocation实现消息转发。通过消息转发实现类似C++中的多重继承。

    iOS中的SEL, 它的作用和C、C++中的函数指针很相似, 通过performSelector:withObject:函数可以直接调用这个消息。

    但是perform相关的这些函数, 有一个局限性, 其参数数量不能超过2个, 否则要做很麻烦的处理, 与之相对, NSInvocation也是一种消息调用的方法, 并且它的参数没有限制。这两种直接调用对象消息的方法, 在IOS4.0之后, 大多被block结构所取代, 只有在很老的兼容性系统中才会使用。

    源码解读

    __objc_msgForward_impcache

    lookUpImpOrForward() 中调用

    // objc-msg-arm64.s
    STATIC_ENTRY __objc_msgForward_impcache
    
    // No stret specialization.
    b   __objc_msgForward
    
    END_ENTRY __objc_msgForward_impcache
    
    __objc_msgForward
    ENTRY __objc_msgForward
    
    adrp    x17, __objc_forward_handler@PAGE
    ldr p17, [x17, __objc_forward_handler@PAGEOFF]
    TailCallFunctionPointer x17
    
    END_ENTRY __objc_msgForward
    
    _objc_forward_handler
    // objc-runtime.mm
    void *_objc_forward_handler = (void*)objc_defaultForwardHandler;
    
    // 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);
    }
    

    这里没有开源, 只有写打印信息; 接下来就需要逆向分析汇编代码了;

    先考虑换一种思路, 如果不实现消息转发, Xcode崩溃会输出函数调用栈:

    Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[Person test]: unrecognized selector sent to instance 0x1004a2f50'
    *** First throw call stack:
    (
        0   CoreFoundation                      0x00007fff2ee22be7 __exceptionPreprocess + 250
        1   libobjc.A.dylib                     0x00007fff67bfa5bf objc_exception_throw + 48
        2   CoreFoundation                      0x00007fff2eea1c77 -[NSObject(NSObject) __retain_OA] + 0
        3   CoreFoundation                      0x00007fff2ed8744b ___forwarding___ + 1427
        4   CoreFoundation                      0x00007fff2ed86e28 _CF_forwarding_prep_0 + 120
        
        6   libdyld.dylib                       0x00007fff68da1cc9 start + 1
        7   ???                                 0x0000000000000001 0x0 + 1
    )
    libc++abi.dylib: terminating with uncaught exception of type NSException
    
    __forwarding__

    其中, ___forwarding___ 会调用 forwardingTargetForSelector:

    // 国外开发者根据汇编写出的伪代码
    int __forwarding__(void *frameStackPointer, int isStret) {
        id receiver = *(id *)frameStackPointer;
        SEL sel = *(SEL *)(frameStackPointer + 8);
        const char *selName = sel_getName(sel);
        Class receiverClass = object_getClass(receiver);
    
        // 调用 forwardingTargetForSelector:
        if (class_respondsToSelector(receiverClass, @selector(forwardingTargetForSelector:))) {
            id forwardingTarget = [receiver forwardingTargetForSelector:sel];
            if (forwardingTarget && forwardingTarget != receiver) {
                if (isStret == 1) {
                    int ret;
                    objc_msgSend_stret(&ret,forwardingTarget, sel, ...);
                    return ret;
                }
                return objc_msgSend(forwardingTarget, sel, ...);
            }
        }
    
        // 僵尸对象
        const char *className = class_getName(receiverClass);
        const char *zombiePrefix = "_NSZombie_";
        size_t prefixLen = strlen(zombiePrefix); // 0xa
        if (strncmp(className, zombiePrefix, prefixLen) == 0) {
            CFLog(kCFLogLevelError,
                  @"*** -[%s %s]: message sent to deallocated instance %p",
                  className + prefixLen,
                  selName,
                  receiver);
            <breakpoint-interrupt>
        }
        // 如果 `forwardingTargetForSelector:` 没有实现, 或者返回了nil; 会调用 `methodSignatureForSelector:`, 要求返回方法签名
        // 调用 methodSignatureForSelector 获取方法签名后, 再调用 forwardInvocation
        if (class_respondsToSelector(receiverClass, @selector(methodSignatureForSelector:))) {
            NSMethodSignature *methodSignature = [receiver methodSignatureForSelector:sel];
            if (methodSignature) {
                BOOL signatureIsStret = [methodSignature _frameDescriptor]->returnArgInfo.flags.isStruct;
                if (signatureIsStret != isStret) {
                    CFLog(kCFLogLevelWarning ,
                          @"*** NSForwarding: warning: method signature and compiler disagree on struct-return-edness of '%s'.  Signature thinks it does%s return a struct, and compiler thinks it does%s.",
                          selName,
                          signatureIsStret ? "" : not,
                          isStret ? "" : not);
                }
                if (class_respondsToSelector(receiverClass, @selector(forwardInvocation:))) {
                    NSInvocation *invocation = [NSInvocation _invocationWithMethodSignature:methodSignature frame:frameStackPointer];
    
                    [receiver forwardInvocation:invocation];
    
                    void *returnValue = NULL;
                    [invocation getReturnValue:&value];
                    return returnValue;
                } else {
                    CFLog(kCFLogLevelWarning ,
                          @"*** NSForwarding: warning: object %p of class '%s' does not implement forwardInvocation: -- dropping message",
                          receiver,
                          className);
                    return 0;
                }
            }
        }
    
        SEL *registeredSel = sel_getUid(selName);
    
        // selector 是否已经在 Runtime 注册过
        if (sel != registeredSel) {
            CFLog(kCFLogLevelWarning ,
                  @"*** NSForwarding: warning: selector (%p) for message '%s' does not match selector known to Objective C runtime (%p)-- abort",
                  sel,
                  selName,
                  registeredSel);
        } // doesNotRecognizeSelector ★
        else if (class_respondsToSelector(receiverClass, @selector(doesNotRecognizeSelector:))) {
            [receiver doesNotRecognizeSelector:sel];
        }
        else {
            CFLog(kCFLogLevelWarning ,
                  @"*** NSForwarding: warning: object %p of class '%s' does not implement doesNotRecognizeSelector: -- abort",
                  receiver,
                  className);
        }
    
        // The point of no return.
        kill(getpid(), 9);
    }
    

    消息转发的应用

    • 容灾处理: 防止找不到方法, 产生崩溃
    @implementation Person
    - (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
        // 本来能调用的方法
        if ([self respondsToSelector:aSelector]) {
            return [super methodSignatureForSelector:aSelector];
        }
        // 找不到的方法
        return [NSMethodSignature signatureWithObjCTypes:"v@:"];
    }
    
    // 找不到的方法, 都会来到这里
    - (void)forwardInvocation:(NSInvocation *)anInvocation {
        // 可以在这里收集找不到的方法
        NSLog(@"找不到%@方法", NSStringFromSelector(anInvocation.selector));
    }
    @end
    
    • NSProxy 专门用来做消息转发

    相关文章

      网友评论

          本文标题:iOS - objc_msgSend()详解

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