Runtime---objc_msgSend執(zhí)行流程

image-20210506163157805
image-20210506163224118
image-20210506163239920
image-20210506163252297
image-20210506163309914
image-20210506163328804
image-20210506163351992

OC方法調(diào)用的本質(zhì):消息發(fā)送機(jī)制 - msg_Send.

OC方法調(diào)用的本質(zhì)就是給對象發(fā)送消息:objc_msgSend(),這個流程可以分為三個階段:

  • 消息發(fā)送
  • 動態(tài)方法解析
  • 消息轉(zhuǎn)發(fā)

下面我們將從源碼上分析這三個過程的具體實現(xiàn).

一消息發(fā)送

消息發(fā)送咋們從[obj message]為出發(fā)點晾腔,從objc源碼里進(jìn)行一次正向梳理尝蠕。

xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m -o main-arm64.cpp
int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
        MJPerson *person = ((MJPerson *(*)(id, SEL))(void *)objc_msgSend)((id)((MJPerson *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("MJPerson"), sel_registerName("alloc")), sel_registerName("init"));
        ((void (*)(id, SEL))(void *)objc_msgSend)((id)person, sel_registerName("personTest"));

        ((void (*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("MJPerson"), sel_registerName("initialize"));


    }
    return 0;
}

通過上面的命令行操作海铆,`[obj message]`編譯之后的底層表示是

((void (*)(id, SEL))(void *)objc_msgSend)((id)person, sel_registerName("personTest"));


```bash
objc_msgSend(person, sel_registerName("personTest"));

其中第一個參數(shù)person就是消息接受者,后面的sel_registerName逗载,可以在objc源碼中搜到它的函數(shù)聲明

SEL _Nonnull sel_registerName(const char * _Nonnull str)

同于OC里面的@selector()

消息轉(zhuǎn)發(fā)送碼分析:

查找源碼步驟:
打開runtime源碼 -> 搜索objc_msgSend -> 找到objc-msg-arm64.s文件 -> 找到ENTRY _objc_msgSend(方法入口):

找到ENTRY _objc_msgSend(方法入口)

匯編 使用 ENTRY + 函數(shù)名字作為一個函數(shù)的入口

END_ENTRY + 函數(shù)名字 函數(shù)結(jié)束

從上圖中可以看到,如果reserver不為nil,就執(zhí)行CacheLookup從緩存中查找,所以我們搜索CacheLookup方法:

如果是命中緩存,找到了方法链烈,那就簡單了厉斟,直接返回并調(diào)用就好了,如果沒找强衡,就會進(jìn)入上圖中的__objc_msgSend_uncached

    STATIC_ENTRY __objc_msgSend_uncached
    UNWIND __objc_msgSend_uncached, FrameWithNoSaves

    // THIS IS NOT A CALLABLE C FUNCTION
    // Out-of-band x16 is the class to search
    
    MethodTableLookup
    br  x17

    END_ENTRY __objc_msgSend_uncached


    STATIC_ENTRY __objc_msgLookup_uncached
    UNWIND __objc_msgLookup_uncached, FrameWithNoSaves

__objc_msgSend_uncached中調(diào)用了MethodTableLookup

我們發(fā)現(xiàn)在MethodTableLookup里面擦秽,調(diào)用了__class_lookupMethodAndLoadCache3函數(shù),而這個函數(shù)在當(dāng)前的匯編代碼里面是找不到實現(xiàn)的漩勤。你去objc源碼進(jìn)行全局搜索感挥,也搜不到.那會不會是用C語言實現(xiàn)的這個方法呢?我們?nèi)サ粢粋€下劃線_然后搜索(<font color='red'>為什么要去掉一個下劃線呢?因為C語言在編譯成匯編語言是,會默認(rèn)在方法前面加一個下劃線_,所以我們在C語言中搜索時要去掉一個</font>),發(fā)現(xiàn)還真搜索到了這個方法:

/***********************************************************************
* _class_lookupMethodAndLoadCache.
* Method lookup for dispatchers ONLY. OTHER CODE SHOULD USE lookUpImp().
* This lookup avoids optimistic cache scan because the dispatcher 
* already tried that.
**********************************************************************/
IMP _class_lookupMethodAndLoadCache3(id obj, SEL sel, Class cls)
{
    return lookUpImpOrForward(cls, sel, obj, 
                              YES/*initialize*/, NO/*cache*/, YES/*resolver*/);
}

進(jìn)入lookUpImpOrForward:

/***********************************************************************
* lookUpImpOrForward.
* The standard IMP lookup. 
* initialize==NO tries to avoid +initialize (but sometimes fails)-------------------------------->??????標(biāo)準(zhǔn)的IMP查找流程
* cache==NO skips optimistic unlocked lookup (but uses cache elsewhere)
* Most callers should use initialize==YES and cache==YES.
* 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 lookUpImpOrNil() instead.
**********************************************************************/
IMP lookUpImpOrForward(Class cls, SEL sel, id inst, 
                       bool initialize, bool cache, bool resolver)
{
    IMP imp = nil;
    bool triedResolver = NO;

    runtimeLock.assertUnlocked();

    // Optimistic cache lookup
    if (cache) {//------------------------------>??????查詢當(dāng)前Class對象的緩存,如果找到方法越败,就返回該方法
        imp = cache_getImp(cls, sel);
        if (imp) return imp;
    }

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

    if (!cls->isRealized()) {//------------------------------>??????當(dāng)前Class如果沒有被realized触幼,就進(jìn)行realize操作
        // Drop the read-lock and acquire the write-lock.
        // realizeClass() checks isRealized() again to prevent
        // a race while the lock is down.
        runtimeLock.unlockRead();
        runtimeLock.write();

        realizeClass(cls);

        runtimeLock.unlockWrite();
        runtimeLock.read();
    }

    if (initialize  &&  !cls->isInitialized()) {//-------------->??????當(dāng)前Class如果沒有初始化,就進(jìn)行初始化操作
        runtimeLock.unlockRead();
        _class_initialize (_class_getNonMetaClass(cls, inst));
        runtimeLock.read();
        // 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
    }

    
 retry:    
    runtimeLock.assertReading();

    // Try this class's cache.//------------------------------>??????嘗試從該Class對象的緩存中查找究飞,如果找到置谦,就跳到done處返回該方法

    imp = cache_getImp(cls, sel);
    if (imp) goto done;

    // Try this class's method lists.//---------------->??????嘗試從該Class對象的方法列表中查找,找到的話亿傅,就緩存到該Class的cache_t里面媒峡,并跳到done處返回該方法
    {
        Method meth = getMethodNoSuper_nolock(cls, sel);
        if (meth) {
            log_and_fill_cache(cls, meth->imp, sel, inst, cls);
            imp = meth->imp;
            goto done;
        }
    }

    // Try superclass caches and method lists.// Try superclass caches and method lists.------>??????進(jìn)入當(dāng)前Class對象的superclass對象
    {
        unsigned attempts = unreasonableClassCount();
        for (Class curClass = cls->superclass;//------>??????該for循環(huán)每循環(huán)一次,就會進(jìn)入上一層的superclass對象袱蜡,進(jìn)行循環(huán)內(nèi)部方法查詢流程
             curClass != nil;
             curClass = curClass->superclass)
        {
            // Halt if there is a cycle in the superclass chain.
            if (--attempts == 0) {
                _objc_fatal("Memory corruption in class list.");
            }
            
            // Superclass cache.------>??????在當(dāng)前superclass對象的緩存進(jìn)行查找
            imp = cache_getImp(curClass, sel);
            if (imp) {
                if (imp != (IMP)_objc_msgForward_impcache) {
                    // Found the method in a superclass. Cache it in this class.
                    log_and_fill_cache(cls, imp, sel, inst, curClass);
                    goto done;//------>??????如果在當(dāng)前superclass的緩存里找到了方法丝蹭,就調(diào)用log_and_fill_cache進(jìn)行方法緩存,注意這里傳入的參數(shù)是cls,也就是將方法緩存到消息接受對象所對應(yīng)的Class對象的cache_t中奔穿,然后跳到done處返回該方法
                }
                else {
                    // Found a forward:: entry in a superclass.
                    // Stop searching, but don't cache yet; call method 
                    // resolver for this class first.
                    break;//---->??????如果緩存里找到的方法是_objc_msgForward_impcache镜沽,就跳出該輪循環(huán),進(jìn)入上一層的superclass贱田,再次進(jìn)行查找
                }
            }
            
            // Superclass method list.---->??????如過畫緩存里面沒有找到方法缅茉,則對當(dāng)前superclass的方法列表進(jìn)行查找
            Method meth = getMethodNoSuper_nolock(curClass, sel);
            if (meth) {
                //------>??????如果在當(dāng)前superclass的方法列表里找到了方法,就調(diào)用log_and_fill_cache進(jìn)行方法緩存男摧,注意這里傳入的參數(shù)是cls蔬墩,也就是將方法緩存到消息接受對象所對應(yīng)的Class對象的cache_t中,然后跳到done處返回該方法
                log_and_fill_cache(cls, meth->imp, sel, inst, curClass);
                imp = meth->imp;
                goto done;
            }
        }
    }

    // No implementation found. Try method resolver once.------>??????如果到基類還沒有找到方法耗拓,就嘗試進(jìn)行方法解析

    if (resolver  &&  !triedResolver) {
        runtimeLock.unlockRead();
        _class_resolveMethod(cls, sel, inst);
        runtimeLock.read();
        // Don't cache the result; we don't hold the lock so it may have 
        // changed already. Re-do the search from scratch instead.
        triedResolver = YES;
        goto retry;
    }

    // No implementation found, and method resolver didn't help. //------>??????如果方法解析不成功拇颅,就進(jìn)行消息轉(zhuǎn)發(fā)
    // Use forwarding.

    imp = (IMP)_objc_msgForward_impcache;
    cache_fill(cls, sel, imp, inst);

 done:
    runtimeLock.unlockRead();

    return imp;
}

關(guān)于上面再方法列表查找的函數(shù)Method meth = getMethodNoSuper_nolock(cls, sel);還需要說明一下,進(jìn)入它的實現(xiàn)

static method_t *
getMethodNoSuper_nolock(Class cls, SEL sel)
{
    runtimeLock.assertLocked();

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

    for (auto mlists = cls->data()->methods.beginLists(), //??遍歷class_rw_t 中的method_list
              end = cls->data()->methods.endLists(); 
         mlists != end;
         ++mlists)
    {
        method_t *m = search_method_list(*mlists, sel);//---??????核心函數(shù)
        if (m) return m;
    }

    return nil;
}

進(jìn)入search_method_list(mlist, sel)看看是如何查找的:

/***********************************************************************
* getMethodNoSuper_nolock
* fixme
* Locking: runtimeLock must be read- or write-locked by the caller
**********************************************************************/
static method_t *search_method_list(const method_list_t *mlist, SEL sel)
{
    int methodListIsFixedUp = mlist->isFixedUp();
    int methodListHasExpectedSize = mlist->entsize() == sizeof(method_t);
    
    if (__builtin_expect(methodListIsFixedUp && methodListHasExpectedSize, 1)) {
        //---??????如果方法列表是經(jīng)過排序的乔询,則進(jìn)行二分查找
        return findMethodInSortedMethodList(sel, mlist);
    } else {
        // Linear search of unsorted method list
        //---??????如果方法列表沒有進(jìn)行排序樟插,則進(jìn)行線性遍歷查找
        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;
}

ok,到現(xiàn)在我們就從源碼層面搞清楚了objc_msgSend()第一階段消息發(fā)送的步驟,我們用一張圖總結(jié)一下:

二:動態(tài)方法解析:

lookUpImpOrForward方法實現(xiàn)中可以看到,如果在消息發(fā)送階段始終沒有找到方法,那么就會進(jìn)入動態(tài)方法解析:

 // No implementation found. Try method resolver once.------>??????如果到基類還沒有找到方法,就嘗試進(jìn)行方法解析

    if (resolver  &&  !triedResolver) {//1????????判斷之前有沒有方法解析過
        runtimeLock.unlockRead();
        _class_resolveMethod(cls, sel, inst);//2????????如果沒有解析過竿刁,調(diào)用_class_resolveMethod
        runtimeLock.read();
        // Don't cache the result; we don't hold the lock so it may have 
        // changed already. Re-do the search from scratch instead.
        triedResolver = YES;
        goto retry;//3????????如果進(jìn)行過動態(tài)方法解析黄锤,就回到retry,再次進(jìn)行消息發(fā)送階段查找方法
    }

    // No implementation found, and method resolver didn't help. //------>??????如果方法解析不成功食拜,就進(jìn)行消息轉(zhuǎn)發(fā)
    // Use forwarding.
    //??????消息轉(zhuǎn)發(fā)階段
    imp = (IMP)_objc_msgForward_impcache;
    cache_fill(cls, sel, imp, inst);

 done:
    runtimeLock.unlockRead();

    return imp;

下面再繼續(xù)看一下方法動態(tài)解析里面的核心函數(shù)_class_resolveMethod(cls, sel, inst);

/***********************************************************************
* _class_resolveMethod
* Call +resolveClassMethod or +resolveInstanceMethod.
* Returns nothing; any result would be potentially out-of-date already.
* Does not check if the method already exists.
**********************************************************************/
void _class_resolveMethod(Class cls, SEL sel, id inst)
{
    if (! cls->isMetaClass()) {//??????判斷當(dāng)前的參數(shù)cls是否是一個meta-class對象
        // try [cls resolveInstanceMethod:sel]
        _class_resolveInstanceMethod(cls, sel, inst);//-->??????
    } 
    else {
        // try [nonMetaClass resolveClassMethod:sel]
        // and [cls resolveInstanceMethod:sel]
        _class_resolveClassMethod(cls, sel, inst);//-->??????
        if (!lookUpImpOrNil(cls, sel, inst, 
                            NO/*initialize*/, YES/*cache*/, NO/*resolver*/)) 
        {
            _class_resolveInstanceMethod(cls, sel, inst);
        }
    }
}

_class_resolveInstanceMethod(cls, sel, inst);鸵熟,進(jìn)入它的函數(shù)實現(xiàn)如下

/***********************************************************************
* _class_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 _class_resolveInstanceMethod(Class cls, SEL sel, id inst)
{
    //---??????查看cls的meta-class對象的方法列表里面是否有SEL_resolveInstanceMethod函數(shù),
    //---??????也就是看是否實現(xiàn)了+(BOOL)resolveInstanceMethod:(SEL)sel方法
    if (! lookUpImpOrNil(cls->ISA(), SEL_resolveInstanceMethod, cls, 
                         NO/*initialize*/, YES/*cache*/, NO/*resolver*/)) 
    {
        // Resolver not implemented.---??????如果沒找到负甸,直接返回流强,
        return;
    }
    //---??????如果找到,則通過objc_msgSend調(diào)用一下+(BOOL)resolveInstanceMethod:(SEL)sel方法
    //---??????完成里面的動態(tài)增加方法的步驟

    BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
    bool resolved = msg(cls, SEL_resolveInstanceMethod, 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(cls, sel, inst, 
                             NO/*initialize*/, YES/*cache*/, NO/*resolver*/);

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

動態(tài)方法解析的核心步驟完成之后惑惶,會一層一層往上返回到lookUpImpOrForward函數(shù)煮盼,跳到retry標(biāo)記處,重新查詢方法带污,因為在方法解析這一步僵控,如果對某個目標(biāo)方法名xxx有過處理,為其動態(tài)增加了方法實現(xiàn)鱼冀,那么再次查詢該方法报破,則一定可以在消息發(fā)送階段被找到并調(diào)用。 對于類方法(+方法)的動態(tài)解析其實跟上面的過程大致相同千绪,只不過解析的時候調(diào)用的+(BOOL)resolveClassMethod:(SEL)sel方法充易,來完成類方法的動態(tài)添加綁定。

我們用代碼驗證一下

//--------------------MJPerson.h-------
@interface MJPerson : NSObject
- (void)test;
@end
//--------------------MJPerson.m-------
#import "MJPerson.h"
#import <objc/runtime.h>

@implementation MJPerson

- (void)other
{
    NSLog(@"%s", __func__);
}
struct method_t {
    SEL sel;
    char *types;
    IMP imp;
};
+ (BOOL)resolveInstanceMethod:(SEL)sel
{
    if (sel == @selector(test)) {
        // 獲取其他方法
        struct method_t *method = (struct method_t *)class_getInstanceMethod(self, @selector(other));

        // 動態(tài)添加test方法的實現(xiàn)
        class_addMethod(self, sel, method->imp, method->types);

        // 返回YES代表有動態(tài)添加方法
        return YES;
    }
    return [super resolveInstanceMethod:sel];
}

@end

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        MJPerson *person = [[MJPerson alloc] init];
        [person test];
    }
    return 0;
}

RUN>

*********************** 運(yùn)行結(jié)果 **************************
2021-05-06 18:17:40.215622+0800 Interview03-動態(tài)方法解析[5697:221883] -[MJPerson other]

動態(tài)方法解析的步驟

關(guān)于動態(tài)方法解析的注意點:

  • 1:通過class_addMethod動態(tài)添加的方法是添加到class_rw_t中的method_list_t中的,我們從源碼中也可以看到,動態(tài)添加方法的實現(xiàn)后,進(jìn)入goto retry,重新進(jìn)入消息發(fā)送階段,從類的cache_t或者class_rw_t中查找
  • 2:動態(tài)添加方法的實現(xiàn)后,會重新進(jìn)入消息發(fā)送階段,重新查找方法

三:消息轉(zhuǎn)發(fā)

經(jīng)過前兩個流程之后荸型,如果還沒能找到方法對應(yīng)的函數(shù)盹靴,說明當(dāng)前類已經(jīng)盡力了,但是確實沒有能力處理目標(biāo)方法,因子只能把方法拋給別人稿静,也就丟給其他的類去處理梭冠,因此最后一個流程為什么叫消息轉(zhuǎn)發(fā),顧名思義改备。
下面控漠,我們來搞定消息轉(zhuǎn)發(fā),入口如下悬钳,位于lookUpImpOrForward函數(shù)的尾部

    // No implementation found, and method resolver didn't help. //------>??????如果方法解析不成功盐捷,就進(jìn)行消息轉(zhuǎn)發(fā)
    // Use forwarding.
    //??????消息轉(zhuǎn)發(fā)階段
    imp = (IMP)_objc_msgForward_impcache;
    cache_fill(cls, sel, imp, inst);

我們進(jìn)入_objc_msgForward_impcache內(nèi)部會發(fā)現(xiàn)沒有別的線索了,只是一個聲明:

#if !OBJC_OLD_DISPATCH_PROTOTYPES
extern void _objc_msgForward_impcache(void);
#else
extern id _objc_msgForward_impcache(id, SEL, ...);
#endif

消息轉(zhuǎn)發(fā)階段后,就會進(jìn)入__forwarding__方法處理.為什么是__forwarding__這個方法呢?我們把resolveInstanceMethod方法注釋掉,看看系統(tǒng)會報什么錯誤:

可以看到從底層上來,調(diào)用了CF框架的_CF_forwarding_prep_0默勾,然后就調(diào)用了___forwarding___碉渡。該函數(shù)就屬于蘋果未開源部分,騰訊課堂iOS底層原理班的分享灾测,國外的大神把消息轉(zhuǎn)發(fā)的流程通過偽代碼寫了出來.

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

    // 調(diào)用 forwardingTargetForSelector:
    if (class_respondsToSelector(receiverClass, @selector(forwardingTargetForSelector:))) {
        id forwardingTarget = [receiver forwardingTargetForSelector:sel];
        if (forwardingTarget && forwardingTarget != receiver) {
            return objc_msgSend(forwardingTarget, sel, ...);
        }
    }

    // 調(diào)用 methodSignatureForSelector 獲取方法簽名后再調(diào)用 forwardInvocation
    if (class_respondsToSelector(receiverClass, @selector(methodSignatureForSelector:))) {
        NSMethodSignature *methodSignature = [receiver methodSignatureForSelector:sel];
        if (methodSignature && class_respondsToSelector(receiverClass, @selector(forwardInvocation:))) {
            NSInvocation *invocation = [NSInvocation _invocationWithMethodSignature:methodSignature frame:frameStackPointer];

            [receiver forwardInvocation:invocation];

            void *returnValue = NULL;
            [invocation getReturnValue:&value];
            return returnValue;
        }
    }

    if (class_respondsToSelector(receiverClass,@selector(doesNotRecognizeSelector:))) {
        [receiver doesNotRecognizeSelector:sel];
    }

    // The point of no return.
    kill(getpid(), 9);
}

-(id)forwardingTargetForSelector:(SEL)aSelector—— __forwarding__首先會看類有沒有實現(xiàn)這個方法爆价,這個方法返回的是一個id類型的轉(zhuǎn)發(fā)對象forwardingTarget,如果其不為空媳搪,則會通過objc_msgSend函數(shù)對其直接發(fā)送消息objc_msgSend(forwardingTarget, sel, ...);,也就是說讓轉(zhuǎn)發(fā)對象forwardingTarget去處理當(dāng)前的方法SEL骤宣。如果forwardingTargetnil秦爆,則進(jìn)入下面的方法

-(NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector——這個方法是讓我們根據(jù)方法選擇器SEL生成一個NSMethodSignature方法簽名并返回,這個方法簽名里面其實就是封裝了返回值類型憔披,參數(shù)類型的信息等限。
__forwarding__會利用這個方法簽名,生成一個NSInvocation芬膝,將其作為參數(shù)望门,調(diào)用- (void)forwardInvocation:(NSInvocation *)anInvocation方法。如果我們在這里沒有返回方法簽名锰霜,系統(tǒng)則認(rèn)為我們徹底不想處理這個方法了筹误,就會調(diào)用doesNotRecognizeSelector:方法拋出經(jīng)典的報錯報錯unrecognized selector sent to instance 0xXXXXXXXX,結(jié)束消息機(jī)制的全部流程癣缅。

- (void)forwardInvocation:(NSInvocation *)anInvocation ——如果我們在上面提供了方法簽名厨剪,__forwarding__則會最終調(diào)用這個方法。在這個方法里面友存,我們會拿到一個參數(shù)(NSInvocation *)anInvocation祷膳,這個anInvocation其實是__forwarding__對如下三個信息的封裝:

  1. anInvocation.target -- 方法調(diào)用者
  2. anInvocation.selector -- 方法名
  3. - (void)getArgument:(void *)argumentLocation atIndex:(NSInteger)idx; -- 方法參數(shù)
    因此在此方法里面屡立,我們可以決定將消息轉(zhuǎn)發(fā)給誰(target)直晨,甚至還可以修改消息的參數(shù),由于anInvocation會存儲消息selector里面帶來的參數(shù),并且可以根據(jù)消息所對應(yīng)的方法簽名確定消息參數(shù)的個數(shù)勇皇,所以我們通過- (void)setArgument:(void *)argumentLocation atIndex:(NSInteger)idx;可以對參數(shù)進(jìn)行修改奕巍。總之你可以按照你的意愿,配置好anInvocation儒士,然后簡單一句[anInvocation invoke];即可完成消息的轉(zhuǎn)發(fā)調(diào)用的止,也可以不做任何處理,輕輕地來着撩,輕輕地走诅福,但是不會導(dǎo)致程序報錯。

我們用代碼驗證一下:

***********************??MJPerson.h ??**************************
#import <Foundation/Foundation.h>

@interface MJPerson : NSObject
- (void)test;
@end

***********************??MJPerson.m ??**************************    
#import "MJPerson.h"
#import <objc/runtime.h>
#import "MJCat.h"

@implementation MJPerson
- (id)forwardingTargetForSelector:(SEL)aSelector
{
    if (aSelector == @selector(test)) {
        // objc_msgSend([[MJCat alloc] init], aSelector)
        return [[MJCat alloc] init];
    }
    return [super forwardingTargetForSelector:aSelector];
}
@end

***********************??MJCat.h ??************************** 
#import <Foundation/Foundation.h>

@interface MJCat : NSObject
- (void)test;
@end
***********************??MJCat.m ??************************** 
#import "MJCat.h"

@implementation MJCat
- (void)test
{
    NSLog(@"%s", __func__);
}
@end
#import <Foundation/Foundation.h>
#import "MJPerson.h"

// 消息轉(zhuǎn)發(fā):將消息轉(zhuǎn)發(fā)給別人

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        MJPerson *person = [[MJPerson alloc] init];
        [person test];
    }
    return 0;
}

RUN>

*********************** 運(yùn)行結(jié)果 **************************
2021-05-06 19:59:53.504938+0800 Interview01-消息轉(zhuǎn)發(fā)[5988:243172] -[MJCat test]

修改??MJPerson.m ??

***********************??MJPerson.m ??**************************    
#import "MJPerson.h"
#import <objc/runtime.h>
#import "MJCat.h"

@implementation MJPerson
// 方法簽名:返回值類型拖叙、參數(shù)類型
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
{
    NSLog(@"methodSignatureForSelector");
    if (aSelector == @selector(test)) {
        return [NSMethodSignature signatureWithObjCTypes:"v16@0:8"];
    }
    return [super methodSignatureForSelector:aSelector];
}
- (void)forwardInvocation:(NSInvocation *)anInvocation
{
    NSLog(@"forwardInvocation");
}
@end

RUN>

*********************** 運(yùn)行結(jié)果 **************************
2021-05-06 20:05:39.826862+0800 Interview01-消息轉(zhuǎn)發(fā)[6060:246757] methodSignatureForSelector
2021-05-06 20:05:39.827236+0800 Interview01-消息轉(zhuǎn)發(fā)[6060:246757] forwardInvocation    

繼續(xù)修改??MJPerson.m ??

***********************??MJPerson.m ??**************************    
#import "MJPerson.h"
#import <objc/runtime.h>
#import "MJCat.h"

@implementation MJPerson
// 方法簽名:返回值類型氓润、參數(shù)類型
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
{
    NSLog(@"methodSignatureForSelector");
    if (aSelector == @selector(test)) {
        return [NSMethodSignature signatureWithObjCTypes:"v16@0:8"];
    }
    return [super methodSignatureForSelector:aSelector];
}
- (void)forwardInvocation:(NSInvocation *)anInvocation
{
    NSLog(@"forwardInvocation");
    [anInvocation invokeWithTarget:[[MJCat alloc] init]];
}
@end

RUN>

*********************** ??運(yùn)行結(jié)果?? **************************
2021-05-06 20:11:46.271551+0800 Interview01-消息轉(zhuǎn)發(fā)[6159:252454] methodSignatureForSelector
2021-05-06 20:11:46.271920+0800 Interview01-消息轉(zhuǎn)發(fā)[6159:252454] forwardInvocation
2021-05-06 20:11:46.271998+0800 Interview01-消息轉(zhuǎn)發(fā)[6159:252454] -[MJCat test]

以上都是以實力方法為例,下面我們把-(void)test;更改為+(void)test:

***********************??MJPerson.h ??**************************
#import <Foundation/Foundation.h>

@interface MJPerson : NSObject
+ (void)test;
@end

***********************??MJPerson.m ??**************************    
#import "MJPerson.h"
#import <objc/runtime.h>
#import "MJCat.h"

@implementation MJPerson
- (id)forwardingTargetForSelector:(SEL)aSelector
{
    if (aSelector == @selector(test)) {
        return [[MJCat alloc] init];
    }
    return [super forwardingTargetForSelector:aSelector];
}
@end

***********************??MJCat.h ??************************** 
#import <Foundation/Foundation.h>

@interface MJCat : NSObject
+ (void)test;
- (void)test;
@end
***********************??MJCat.m ??************************** 
#import "MJCat.h"

@implementation MJCat

+ (void)test
{
    NSLog(@"%s", __func__);
}

- (void)test
{
    NSLog(@"%s", __func__);
}

@end

修改??MJPerson.m ??

***********************??MJPerson.m ??**************************    
#import "MJPerson.h"
#import <objc/runtime.h>
#import "MJCat.h"

@implementation MJPerson
+ (id)forwardingTargetForSelector:(SEL)aSelector
{
    if (aSelector == @selector(test)) {
        return [[MJCat alloc] init];
    }
    return [super forwardingTargetForSelector:aSelector];
}
@end

繼續(xù)修改??MJPerson.m ??

***********************??MJPerson.m ??**************************    
#import "MJPerson.h"
#import <objc/runtime.h>
#import "MJCat.h"

@implementation MJPerson
+ (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
{
    if (aSelector == @selector(test)) return [NSMethodSignature signatureWithObjCTypes:"v@:"];
    
    return [super methodSignatureForSelector:aSelector];
}

+ (void)forwardInvocation:(NSInvocation *)anInvocation
{
    NSLog(@"1123");
}
@end

RUN>

*********************** ??運(yùn)行結(jié)果?? **************************
2021-05-06 20:21:55.074676+0800 Interview01-消息轉(zhuǎn)發(fā)[6264:259359] 1123

到這里我們就搞清楚了消息轉(zhuǎn)發(fā)的所有流程和細(xì)節(jié)

image-20210506202309515
特別備注

本系列文章總結(jié)自MJ老師在騰訊課堂iOS底層原理班(下)/OC對象/關(guān)聯(lián)對象/多線程/內(nèi)存管理/性能優(yōu)化,相關(guān)圖片素材均取自課程中的課件薯鳍。如有侵權(quán)咖气,請聯(lián)系我刪除,謝謝挖滤!

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末崩溪,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子斩松,更是在濱河造成了極大的恐慌伶唯,老刑警劉巖,帶你破解...
    沈念sama閱讀 221,695評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件惧盹,死亡現(xiàn)場離奇詭異乳幸,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)钧椰,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,569評論 3 399
  • 文/潘曉璐 我一進(jìn)店門粹断,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人嫡霞,你說我怎么就攤上這事瓶埋。” “怎么了秒际?”我有些...
    開封第一講書人閱讀 168,130評論 0 360
  • 文/不壞的土叔 我叫張陵悬赏,是天一觀的道長。 經(jīng)常有香客問我娄徊,道長闽颇,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,648評論 1 297
  • 正文 為了忘掉前任寄锐,我火速辦了婚禮兵多,結(jié)果婚禮上尖啡,老公的妹妹穿的比我還像新娘。我一直安慰自己剩膘,他們只是感情好衅斩,可當(dāng)我...
    茶點故事閱讀 68,655評論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著怠褐,像睡著了一般畏梆。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上奈懒,一...
    開封第一講書人閱讀 52,268評論 1 309
  • 那天奠涌,我揣著相機(jī)與錄音,去河邊找鬼磷杏。 笑死溜畅,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的极祸。 我是一名探鬼主播慈格,決...
    沈念sama閱讀 40,835評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼遥金!你這毒婦竟也來了浴捆?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,740評論 0 276
  • 序言:老撾萬榮一對情侶失蹤汰规,失蹤者是張志新(化名)和其女友劉穎汤功,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體溜哮,經(jīng)...
    沈念sama閱讀 46,286評論 1 318
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,375評論 3 340
  • 正文 我和宋清朗相戀三年色解,在試婚紗的時候發(fā)現(xiàn)自己被綠了茂嗓。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,505評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡科阎,死狀恐怖述吸,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情锣笨,我是刑警寧澤蝌矛,帶...
    沈念sama閱讀 36,185評論 5 350
  • 正文 年R本政府宣布,位于F島的核電站错英,受9級特大地震影響入撒,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜椭岩,卻給世界環(huán)境...
    茶點故事閱讀 41,873評論 3 333
  • 文/蒙蒙 一茅逮、第九天 我趴在偏房一處隱蔽的房頂上張望璃赡。 院中可真熱鬧,春花似錦献雅、人聲如沸碉考。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,357評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽侯谁。三九已至,卻和暖如春章钾,著一層夾襖步出監(jiān)牢的瞬間墙贱,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,466評論 1 272
  • 我被黑心中介騙來泰國打工伍玖, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留嫩痰,地道東北人。 一個月前我還...
    沈念sama閱讀 48,921評論 3 376
  • 正文 我出身青樓窍箍,卻偏偏與公主長得像串纺,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子椰棘,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,515評論 2 359

推薦閱讀更多精彩內(nèi)容