Runtime---objc_msgSend执行流程

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

OC方法调用的本质:消息发送机制 - msg_Send.

OC方法调用的本质就是给对象发送消息:objc_msgSend(),这个流程可以分为三个阶段:

  • 消息发送
  • 动态方法解析
  • 消息转发

下面我们将从源码上分析这三个过程的具体实现.

一消息发送

消息发送咋们从[obj message]为出发点,从objc源码里进行一次正向梳理。

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

其中第一个参数person就是消息接受者,后面的sel_registerName,可以在objc源码中搜到它的函数声明

SEL _Nonnull sel_registerName(const char * _Nonnull str)

同于OC里面的@selector()

消息转发送码分析:

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

找到ENTRY _objc_msgSend(方法入口)

汇编 使用 ENTRY + 函数名字作为一个函数的入口

END_ENTRY + 函数名字 函数结束

从上图中可以看到,如果reserver不为nil,就执行CacheLookup从缓存中查找,所以我们搜索CacheLookup方法:

如果是命中缓存,找到了方法,那就简单了,直接返回并调用就好了,如果没找,就会进入上图中的__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中调用了MethodTableLookup

我们发现在MethodTableLookup里面,调用了__class_lookupMethodAndLoadCache3函数,而这个函数在当前的汇编代码里面是找不到实现的。你去objc源码进行全局搜索,也搜不到.那会不会是用C语言实现的这个方法呢?我们去掉一个下划线_然后搜索(<font color='red'>为什么要去掉一个下划线呢?因为C语言在编译成汇编语言是,会默认在方法前面加一个下划线_,所以我们在C语言中搜索时要去掉一个</font>),发现还真搜索到了这个方法:

/***********************************************************************
* _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*/);
}

进入lookUpImpOrForward:

/***********************************************************************
* lookUpImpOrForward.
* The standard IMP lookup. 
* initialize==NO tries to avoid +initialize (but sometimes fails)-------------------------------->⚠️⚠️⚠️标准的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) {//------------------------------>⚠️⚠️⚠️查询当前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()) {//------------------------------>⚠️⚠️⚠️当前Class如果没有被realized,就进行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()) {//-------------->⚠️⚠️⚠️当前Class如果没有初始化,就进行初始化操作
        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.------>⚠️⚠️⚠️进入当前Class对象的superclass对象
    {
        unsigned attempts = unreasonableClassCount();
        for (Class curClass = cls->superclass;//------>⚠️⚠️⚠️该for循环每循环一次,就会进入上一层的superclass对象,进行循环内部方法查询流程
             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.------>⚠️⚠️⚠️在当前superclass对象的缓存进行查找
            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;//------>⚠️⚠️⚠️如果在当前superclass的缓存里找到了方法,就调用log_and_fill_cache进行方法缓存,注意这里传入的参数是cls,也就是将方法缓存到消息接受对象所对应的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,就跳出该轮循环,进入上一层的superclass,再次进行查找
                }
            }
            
            // Superclass method list.---->⚠️⚠️⚠️如过画缓存里面没有找到方法,则对当前superclass的方法列表进行查找
            Method meth = getMethodNoSuper_nolock(curClass, sel);
            if (meth) {
                //------>⚠️⚠️⚠️如果在当前superclass的方法列表里找到了方法,就调用log_and_fill_cache进行方法缓存,注意这里传入的参数是cls,也就是将方法缓存到消息接受对象所对应的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.------>⚠️⚠️⚠️如果到基类还没有找到方法,就尝试进行方法解析

    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. //------>⚠️⚠️⚠️如果方法解析不成功,就进行消息转发
    // Use forwarding.

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

 done:
    runtimeLock.unlockRead();

    return imp;
}

关于上面再方法列表查找的函数Method meth = getMethodNoSuper_nolock(cls, sel);还需要说明一下,进入它的实现

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);//---⚠️⚠️⚠️核心函数
        if (m) return m;
    }

    return nil;
}

进入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)) {
        //---⚠️⚠️⚠️如果方法列表是经过排序的,则进行二分查找
        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;
}

ok,到现在我们就从源码层面搞清楚了objc_msgSend()第一阶段消息发送的步骤,我们用一张图总结一下:

二:动态方法解析:

lookUpImpOrForward方法实现中可以看到,如果在消息发送阶段始终没有找到方法,那么就会进入动态方法解析:

 // No implementation found. Try method resolver once.------>⚠️⚠️⚠️如果到基类还没有找到方法,就尝试进行方法解析

    if (resolver  &&  !triedResolver) {//1️⃣⚽️⚽️⚽️判断之前有没有方法解析过
        runtimeLock.unlockRead();
        _class_resolveMethod(cls, sel, inst);//2️⃣⚽️⚽️⚽️如果没有解析过,调用_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️⃣⚽️⚽️⚽️如果进行过动态方法解析,就回到retry,再次进行消息发送阶段查找方法
    }

    // No implementation found, and method resolver didn't help. //------>⚠️⚠️⚠️如果方法解析不成功,就进行消息转发
    // Use forwarding.
    //📦📦📦消息转发阶段
    imp = (IMP)_objc_msgForward_impcache;
    cache_fill(cls, sel, imp, inst);

 done:
    runtimeLock.unlockRead();

    return imp;

下面再继续看一下方法动态解析里面的核心函数_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()) {//⚽️⚽️⚽️判断当前的参数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);,进入它的函数实现如下

/***********************************************************************
* _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函数,
    //---⚠️⚠️⚠️也就是看是否实现了+(BOOL)resolveInstanceMethod:(SEL)sel方法
    if (! lookUpImpOrNil(cls->ISA(), SEL_resolveInstanceMethod, cls, 
                         NO/*initialize*/, YES/*cache*/, NO/*resolver*/)) 
    {
        // Resolver not implemented.---⚠️⚠️⚠️如果没找到,直接返回,
        return;
    }
    //---⚠️⚠️⚠️如果找到,则通过objc_msgSend调用一下+(BOOL)resolveInstanceMethod:(SEL)sel方法
    //---⚠️⚠️⚠️完成里面的动态增加方法的步骤

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

动态方法解析的核心步骤完成之后,会一层一层往上返回到lookUpImpOrForward函数,跳到retry标记处,重新查询方法,因为在方法解析这一步,如果对某个目标方法名xxx有过处理,为其动态增加了方法实现,那么再次查询该方法,则一定可以在消息发送阶段被找到并调用。 对于类方法(+方法)的动态解析其实跟上面的过程大致相同,只不过解析的时候调用的+(BOOL)resolveClassMethod:(SEL)sel方法,来完成类方法的动态添加绑定。

我们用代码验证一下

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

        // 动态添加test方法的实现
        class_addMethod(self, sel, method->imp, method->types);

        // 返回YES代表有动态添加方法
        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>

*********************** 运行结果 **************************
2021-05-06 18:17:40.215622+0800 Interview03-动态方法解析[5697:221883] -[MJPerson other]

动态方法解析的步骤

关于动态方法解析的注意点:

  • 1:通过class_addMethod动态添加的方法是添加到class_rw_t中的method_list_t中的,我们从源码中也可以看到,动态添加方法的实现后,进入goto retry,重新进入消息发送阶段,从类的cache_t或者class_rw_t中查找
  • 2:动态添加方法的实现后,会重新进入消息发送阶段,重新查找方法

三:消息转发

经过前两个流程之后,如果还没能找到方法对应的函数,说明当前类已经尽力了,但是确实没有能力处理目标方法,因子只能把方法抛给别人,也就丢给其他的类去处理,因此最后一个流程为什么叫消息转发,顾名思义。
下面,我们来搞定消息转发,入口如下,位于lookUpImpOrForward函数的尾部

    // No implementation found, and method resolver didn't help. //------>⚠️⚠️⚠️如果方法解析不成功,就进行消息转发
    // Use forwarding.
    //📦📦📦消息转发阶段
    imp = (IMP)_objc_msgForward_impcache;
    cache_fill(cls, sel, imp, inst);

我们进入_objc_msgForward_impcache内部会发现没有别的线索了,只是一个声明:

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

消息转发阶段后,就会进入__forwarding__方法处理.为什么是__forwarding__这个方法呢?我们把resolveInstanceMethod方法注释掉,看看系统会报什么错误:

可以看到从底层上来,调用了CF框架的_CF_forwarding_prep_0,然后就调用了___forwarding___。该函数就属于苹果未开源部分,腾讯课堂iOS底层原理班的分享,国外的大神把消息转发的流程通过伪代码写了出来.

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) {
            return objc_msgSend(forwardingTarget, sel, ...);
        }
    }

    // 调用 methodSignatureForSelector 获取方法签名后再调用 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__首先会看类有没有实现这个方法,这个方法返回的是一个id类型的转发对象forwardingTarget,如果其不为空,则会通过objc_msgSend函数对其直接发送消息objc_msgSend(forwardingTarget, sel, ...);,也就是说让转发对象forwardingTarget去处理当前的方法SEL。如果forwardingTargetnil,则进入下面的方法

-(NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector——这个方法是让我们根据方法选择器SEL生成一个NSMethodSignature方法签名并返回,这个方法签名里面其实就是封装了返回值类型,参数类型的信息。
__forwarding__会利用这个方法签名,生成一个NSInvocation,将其作为参数,调用- (void)forwardInvocation:(NSInvocation *)anInvocation方法。如果我们在这里没有返回方法签名,系统则认为我们彻底不想处理这个方法了,就会调用doesNotRecognizeSelector:方法抛出经典的报错报错unrecognized selector sent to instance 0xXXXXXXXX,结束消息机制的全部流程。

- (void)forwardInvocation:(NSInvocation *)anInvocation ——如果我们在上面提供了方法签名,__forwarding__则会最终调用这个方法。在这个方法里面,我们会拿到一个参数(NSInvocation *)anInvocation,这个anInvocation其实是__forwarding__对如下三个信息的封装:

  1. anInvocation.target -- 方法调用者
  2. anInvocation.selector -- 方法名
  3. - (void)getArgument:(void *)argumentLocation atIndex:(NSInteger)idx; -- 方法参数
    因此在此方法里面,我们可以决定将消息转发给谁(target),甚至还可以修改消息的参数,由于anInvocation会存储消息selector里面带来的参数,并且可以根据消息所对应的方法签名确定消息参数的个数,所以我们通过- (void)setArgument:(void *)argumentLocation atIndex:(NSInteger)idx;可以对参数进行修改。总之你可以按照你的意愿,配置好anInvocation,然后简单一句[anInvocation invoke];即可完成消息的转发调用,也可以不做任何处理,轻轻地来,轻轻地走,但是不会导致程序报错。

我们用代码验证一下:

***********************🕴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"

// 消息转发:将消息转发给别人

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

RUN>

*********************** 运行结果 **************************
2021-05-06 19:59:53.504938+0800 Interview01-消息转发[5988:243172] -[MJCat test]

修改🕴MJPerson.m 🕴

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

@implementation MJPerson
// 方法签名:返回值类型、参数类型
- (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>

*********************** 运行结果 **************************
2021-05-06 20:05:39.826862+0800 Interview01-消息转发[6060:246757] methodSignatureForSelector
2021-05-06 20:05:39.827236+0800 Interview01-消息转发[6060:246757] forwardInvocation    

继续修改🕴MJPerson.m 🕴

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

@implementation MJPerson
// 方法签名:返回值类型、参数类型
- (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>

*********************** 👁运行结果👁 **************************
2021-05-06 20:11:46.271551+0800 Interview01-消息转发[6159:252454] methodSignatureForSelector
2021-05-06 20:11:46.271920+0800 Interview01-消息转发[6159:252454] forwardInvocation
2021-05-06 20:11:46.271998+0800 Interview01-消息转发[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

继续修改🕴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>

*********************** 👁运行结果👁 **************************
2021-05-06 20:21:55.074676+0800 Interview01-消息转发[6264:259359] 1123

到这里我们就搞清楚了消息转发的所有流程和细节

image-20210506202309515
特别备注

本系列文章总结自MJ老师在腾讯课堂iOS底层原理班(下)/OC对象/关联对象/多线程/内存管理/性能优化,相关图片素材均取自课程中的课件。如有侵权,请联系我删除,谢谢!

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,793评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,567评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,342评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,825评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,814评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,680评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,033评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,687评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,175评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,668评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,775评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,419评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,020评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,978评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,206评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,092评论 2 351
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,510评论 2 343

推荐阅读更多精彩内容