从Runtime源码角度,探究消息传递流程

从Runtime源码角度,探究消息传递流程_第1张图片
image.png

美女图片,提神醒脑。

Runtime是C、C++、汇编写的一套API,特性主要是消息(方法)传递,如果消息(方法)在对象中找不到,就进行转发,我们从以下几个方法去探究以下Runtime的实现机制:

  • Runtime介绍
  • Runtime消息传递
  • Runtime消息转发
  • Runtime应用

Runtime介绍

OC是一个动态语言,这意味着它不仅需要一个编译器,也需要一个运行时系统来动态创建类和对象,进行消息传递和转发。理解Runtime机制可以帮助更好的了解这个语言,了解底层,帮助我们可以更好的使用上层提供的方法。

  • 编译:编译就是编译器把源代码翻译成机器能够识别的代码
  • 运行:代码跑起来,被装载到内存中去

高级编程语言想要成为可执行文件需要先编译为汇编语言再汇编为机器语言,机器语言也是计算机能够识别的唯一语言,但是OC并不能直接编译为汇编语言,而是要先转写为纯C语言再进行编译和汇编的操作,从OC到C的过渡就是由runtime来实现的。然而我们使用OC进行面向对象开发,而C语言更多的是面向过程开发,这就需要将面向对象的类,转变为面向过程的结构体。

常见结构体介绍

我们知道对象本身是一个结构体,类也是,这里先来提前了解一下相关的概念:

  • 类对象(objc_class)
    OC类是由Class类型表示的,它实际上是一个指向objc_class结构体的指针:
typedef struct objc_class *Class;

objc/runtime.hobjc_class结构体的定义如下:

struct objc_class {
    Class _Nonnull isa  OBJC_ISA_AVAILABILITY;

#if !__OBJC2__
    Class _Nullable super_class                              父类;
    const char * _Nonnull name                               名字;
    long version                                             版本;
    long info                                                信息;
    long instance_size                                       实例大小;
    struct objc_ivar_list * _Nullable ivars                  实例变量列表;
    struct objc_method_list * _Nullable * _Nullable methodLists             方法列表;
    struct objc_cache * _Nonnull cache                       方法缓存;
    struct objc_protocol_list * _Nullable protocols          协议列表;
#endif

} OBJC2_UNAVAILABLE;

类对象就是一个结构体struct objc_class,结构体中包含的信息就是这些,这个结构体存放的数据被称为元数据(metadata)。
这个结构体的第一个成员变量也是isa指针,说明Class本身其实也是一个对象,称为类对象,类对象在编译器产生,用于创建实例对象。

我们知道实例对象的isa指针指向该对象所属的类,那么类对象的isa指针指向谁呢?就是元类

  • 实例(objc_object)
/// Represents an instance of a class.
struct objc_object {
    Class isa  OBJC_ISA_AVAILABILITY;
};

/// A pointer to an instance of a class.
typedef struct objc_object *id;

通过上面我们很容易看到,对象就是一个指向类对象生成的实例对象的指针,类对象的元数据存储的都是如何创建一个实例的相关信息,那么类对象和类方法应该从哪里创建呢?
就是从类对象的isa指针指向的结构体创建,上面我们提到过,类对象的isa指针指向元类(metaclass),元类中保存了创建类对象以及类方法所需的所有信息,下面可以看下isa的走位图:

从Runtime源码角度,探究消息传递流程_第2张图片
isa走位流程.png

通过上面这个图,我们可以看出

  • 对象的isa指针指向所属类,类中存储着对象可以调用的方法,也就是实例方法

  • 类对象的isa指针指向所属的元类,元类中存储着类对象可以调用的方法,类方法,但是类方法是以实例方法的形式存储在元类中

  • 所有元类的isa指针指向根元类

  • 根元类的父类是NSObject

  • NSObject的isa指针指向根元类

  • 元类(meta class)
    在上面我们多次提到了元类这个概念,元类(meta class)是一个类对象的类。
    上面我们提到过,类也可以看成一个对象,就是类对象,我们给类对象发送消息(即调用类方法)。为了调用类方法,这个类的isa指针必须指向一个包含这些类方法的一个objc_class结构体,因此,就引出了meta class的概念,元类中保存了一个创建类对象以及类方法所需的所有信息。
    NSObject的元类就是根元类,也是其余类的元类的父类,根元类的isa指针还是指向自己的,根元类的父类是NSObject。

我们可以看下面获取类方法的源码:

Method class_getClassMethod(Class cls, SEL sel)
{
    if (!cls  ||  !sel) return nil;
    return class_getInstanceMethod(cls->getMeta(), sel);
}

实际上也是获取该类所属元类的实例方法。
或者直接打印使用class_getClassMethod和class_getInstanceMethod两种方法获取到的方法的method地址进行验证。

  • Method(objc_method)方法
    直接上定义:
typedef struct objc_method *Method;

struct objc_method {
    SEL _Nonnull method_name                                 方法名;
    char * _Nullable method_types                            方法类型;
    IMP _Nonnull method_imp                                    方法实现;
}  

这个结构体重,我们看到了SELIMP,说明SELIMP都是Method的一部分。

  • SEL(objc_selector)
    定义如下:
/// An opaque type that represents a method selector.代表一个方法的不透明类型
typedef struct objc_selector *SEL;

方法选择器,也是objc_msgSend函数的第二个参数,它是selecotrOC中的标识类型(Swift中是Selector类)。selector是方法选择器,可以理解为区分方法的ID,而这个ID的数据结构是SEL

@property SEL selector;

可以看到selector是SEL的一个实例。

其实selector就是个映射到方法的C字符串,可以使用OC编译器命令@selector()或者Runtimesel_registerName函数来获得一个SEL类型的方法选择器。

  • 同一个类,selector不能重复
  • 不同的类,selector可以重复

所以,我们不能跟C一样,使用函数重载,同一个类中不能拥有两个方法名相同的方法。

  • IMP
/// A pointer to the function of a method implementation.  指向一个方法实现的指针
typedef id (*IMP)(id, SEL, ...); 
#endif

就是指向最终实现程序的内存地址的指针

在iOS的Runtime中,Method通过selecotrIMP,实现了快速查询方法及实现

  • 类缓存(objc_cache)
    Objective-C运行时,通过跟踪它的isa指针检查对象时,它可以找到包含所有方法的方法列表,然而,可能只用到其中的一部分,每次都要查所有的,很费时间。所以类实现一个缓存,这个缓存中存储着之前访问过的方法,因为你可能以后再次调用该消息,提高方法查找性能。

  • Category(objc_category)
    Category表示一个指向分类的结构体的指针:

struct category_t { 
    const char *name; 
    classref_t cls; 
    struct method_list_t *instanceMethods; 
    struct method_list_t *classMethods;
    struct protocol_list_t *protocols;
    struct property_list_t *instanceProperties;
};

分类以及其相关的探究在另一篇文章中有介绍。

Runtime消息传递 & 消息查找

比如我们给一个Person类的.h文件增加了一个方法:

- (void)play;

我们创建一个Person类的实例,去调用play方法:

[person play];

编译一下,完全没问题,运行的时候,会发现,崩溃了,在调用play方法,到产生崩溃之间,系统是做了什么,又是如何处理的呢?

首先,我们或多或少都知道,对象的方法调用,编译器都会转成消息发送objc_msgSend(obj, foo),Runtime时执行的流程大概是这样的:

  • 通过person的isa指针,找到person所属的类,也就是Person
  • 在Person这个类的 method list(方法列表)中找 play方法
  • 如果Person类的方法列表没有找到foo,继续递归往它的superclass中找,一直找到NSObject的方法列表
  • 如果这个过程中能找到那就直接去执行方法的实现IMP
  • 如果这个过程(消息查找)没有找到,那么就会进入消息转发流程(下面会介绍)
  • 如果消息转发没有处理,那么就会报错:没有找到这个方法的实现

我们这里先分析消息查找的流程,是如何通过sel查找到对应的imp的?
我们肯定是要从objc_msgSend这个方法的实现入手,这个方法是由汇编和C共同完成的,这里我们主要看C的实现的部分,也能体现出方法的查找流程。

objc_msgSend 会有两种方式查找:

  • 快速 汇编 在缓存中找 通过SEL找imp(哈希表),找不到就下面那个过程
  • 慢速 C C++,找到了会存入缓存,没找到,另外一个复杂的过程:消息转发

objc_msgSend 是汇编写的,为什么用汇编写的?

  • C不可能写一个函数,保留一个未知的参数(比如一个实例对象,运行时才知道它是什么类型的),跳转到任意的指针
  • 汇编可以啊,有寄存器

首先,汇编语言,缓存中找那个imp,如果没有找到会调用 MethodTableLoopUp方法,方法列表中查找。

MethodTableLoopUp 中调用 _class_lookupMethodAndLoadCache3(核心方法)
去掉一个
来搜索,查找这个_class_lookupMethodAndLoadCache3方法
这个方法我们在下面还会用到,因为它也包含了消息转发后的逻辑处理:

IMP _class_lookupMethodAndLoadCache3(id obj, SEL sel, Class cls)
{
    return lookUpImpOrForward(cls, sel, obj, 
                              YES/*initialize*/, NO/*cache*/, YES/*resolver*/);
}

next>

// cls 是一个类对象
IMP lookUpImpOrForward(Class cls, SEL sel, id inst, 
                       bool initialize, bool cache, bool resolver)
{
    IMP imp = nil;
    bool triedResolver = NO;

    runtimeLock.assertUnlocked();

    // 再从缓存中查找
    if (cache) {
        // 这次也是汇编查找
        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.lock();
    checkIsKnownClass(cls);

// 判断实现
    if (!cls->isRealized()) {
// 实现类 DATA里面一系列 赋值
        realizeClass(cls);
    }

    if (initialize  &&  !cls->isInitialized()) {
        runtimeLock.unlock();
        _class_initialize (_class_getNonMetaClass(cls, inst));
        runtimeLock.lock();
        // 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.assertLocked();

    // Try this class's cache.
// 为什么再缓存获取一次?(remap(cls) 重映射)
    imp = cache_getImp(cls, sel);
    if (imp) goto done;

    // 查找当前类的方法列表
    {
        Method meth = getMethodNoSuper_nolock(cls, sel);
        if (meth) {
           // 如果找到了,就存如缓存,然后返回
            log_and_fill_cache(cls, meth->imp, sel, inst, cls);
            imp = meth->imp;
            goto done;
        }
    }

    // 递归查找父类的缓存 和 父类的方法列表
    {
        unsigned attempts = unreasonableClassCount();
        for (Class curClass = cls->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.
            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;
                }
                else {
                    // Found a forward:: entry in a superclass.
                    // Stop searching, but don't cache yet; call method 
                    // resolver for this class first.
                    break;
                }
            }
            
            // Superclass method list.
            Method meth = getMethodNoSuper_nolock(curClass, sel);
            if (meth) {
                // 缓存赋值
                log_and_fill_cache(cls, meth->imp, sel, inst, curClass);
                imp = meth->imp;
                goto done;
            }
        }
    }

    // No implementation found. Try method resolver once.
// 没有找到imp,会进行动态解析,注意triedResolver,初始为NO,解析一次设置为YES了,所以只会动态解析一次
    if (resolver  &&  !triedResolver) {
        runtimeLock.unlock();
         // 这一步,如果处理之后,也就是进行了消息动态方法解析之后
        _class_resolveMethod(cls, sel, inst);
        runtimeLock.lock();
        // 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;
// 执行完消息动态方法解析之后,会再retry,再次进入上面递归查找方法,此时如果动态方法解析中处理了,那么这一次就能被找到,然后执行了
        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.unlock();
    return imp;
}

通过上面我们可以得出方法查找的流程是:

  • 先从缓存中找
  • 缓存中没有,从该类的方法列表中找
  • 该类的方法列表中没有,递归从父类的缓存和父类的方法列表中找
  • 如果在第2、3步找到,就会加入到方法缓存中,然后返回

Runtime消息转发

上面介绍了发送消息之后,方法查找的流程,如果上面那些操作都没有找到对应的方法实现,那么就会进入到消息转发的流程:

  • 动态方法解析
  • 备用接受者
  • 完整消息转发

下面,我们一个一个进行介绍:

  • 动态方法解析
    首先,OC运行时会调用+resolveInstanceMethod:或者+resolveClassMethod:,让你有机会提供一个函数实现,如果你添加了函数并返回YES,那运行时系统就会重新启动一次消息发送的过程。
    例子如下:
- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
    //执行foo函数
    [self performSelector:@selector(test:)];
}

+ (BOOL)resolveInstanceMethod:(SEL)sel {
    if (sel == @selector(test:)) {//如果是执行foo函数,就动态解析,指定新的IMP
        class_addMethod([self class], sel, (IMP) testMethod, "v@:");
        return YES;
    }
    return [super resolveInstanceMethod:sel];
}

void testMethod(id obj, SEL _cmd) {
    NSLog(@"Doing test");//新的test函数
}

从上面可以看到,虽然没有实现test:这个函数,但是我们通过class_addMethod动态添加testMethod函数,并执行testMethod这个函数的IMP,也就不会报错了。

我们从Runtime源码入手,看一下这一块的源码,在上面的_class_lookupMethodAndLoadCache3方法实现中,我们发现消息查找无果后,会进入消息动态解析的流程,调用_class_resolveMethod这个方法:

void _class_resolveMethod(Class cls, SEL sel, id inst)
{
    if (! cls->isMetaClass()) {
        // try [cls resolveInstanceMethod:sel]
// 这个里面去查找cls的元类是否有 resolveInstanceMethod,又是递归查找,但是这一次不会再有消息动态解析,因为会产生死循环,也是因为NSObject已经实现了resolveInstanceMethod,同时也有外部传入参数resolver的控制
        _class_resolveInstanceMethod(cls, sel, inst);
    } 
    else {
        // try [nonMetaClass resolveClassMethod:sel]
        // and [cls resolveInstanceMethod:sel]
        //(类方法) - 元类(实例) - 根元类(实例) - NSObject (实例方法)
        // 这里要体会一下根元类的父类是NSObject NSObject的类方法以实例方法的形式存储在根元类中
        _class_resolveClassMethod(cls, sel, inst);
        if (!lookUpImpOrNil(cls, sel, inst, 
                            NO/*initialize*/, YES/*cache*/, NO/*resolver*/)) 
        {
            _class_resolveInstanceMethod(cls, sel, inst);
        }
    }
}

之后,调用动态方法解析的方法

static void _class_resolveInstanceMethod(Class cls, SEL sel, id inst)
{
    // 判断cls->isa 元类 及其父类 是否实现了 SEL_resolveInstanceMethod,如果自己没有实现,会查找到NSObject
    if (! lookUpImpOrNil(cls->ISA(), SEL_resolveInstanceMethod, cls, 
                         NO/*initialize*/, YES/*cache*/, NO/*resolver*/)) 
    {
        // Resolver not implemented.
        return;
    }

    BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
    ****这里是调用方法 SEL_resolveInstanceMethod*****
    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
// 调用动态方法解析完成之后,如果用户已经处理了,给sel添加了对应的imp,此时会再次进入消息递归查找,此时就能找到了,然后可以执行了,很完美
    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));
        }
    }
}

看上面的代码,最终会调用SEL_resolveInstanceMethod,如果自己实现了这个方法,我们可以在这里面去动态添加那个方法了。
添加了那个方法之后,会再次递归查找本类 父类的方法列表,此时就能找到然后执行了。

  • 消息转发类方法处理
    我们再次需要明白:
    对象方法的存储 - 类
    类方法的存储 - 元类,相当于对象方法
    所以类方法的查找就需要找该类所属的元类的方法列表是否有同名的对象方法。

比如Person 继承NSObject,Person 有 +walk 类方法声明,但是没有实现,NSObject+Test.m 分类里面有 -walk实例方法的实现,这样在执行[Person walk]的时候,也不会进入消息动态解析。
因为Person的+walk类方法查找是,先查找Person所属的元类的实例方法,如果没有,查其父类,一直到根元类,也没有,再查找根元类的父类:NSObject,因为我们通过NSObject+Test.m这个分类中,添加了-walk的方法实现,所以就找到了walk的方法实现,可以执行。

类方法查找的时候,也会执行下面这个方法:

IMP lookUpImpOrForward(Class cls, SEL sel, id inst, 
                       bool initialize, bool cache, bool resolver)

此时 cls 是元类,inst 是一个类对象,在查找一个类的类方法的时候,它会查找类所属的元类,及其元类的父类,一直到根元类,最后到根元类的父类NSObject。

比如上面那个例子,我们想要找Person的+walk这个类方法,因为类方法存储在元类里面,我们会找Person的元类,递归找到Person元类的父类,一直到根元类,都没有,此时根元类的父类是NSObject,发现有-walk 这个实例方法(我们知道类方法,在元类中以实例方法进行存储),然后就调用-walk方法了。

NSObject的isa指向根元类,也就是NSObject的元类是根元类,所以NSObject的类方法,会存到根元类里面,以实例方法的形式。

所以类方法可以在NSObject中,以类方法或者实例方法实现。
再比如上面那个例子,如果我们在NSObject分类中 实现 +walk类方法,不去用实例方法了,此时+walk这个方法,会以实例方法的姿态存储到NSObject的元类(根元类)中,当Person 调用 +walk 方法查找的时候,会查找到根元类中,在根元类中找到 - walk方法,去调用,不会再去查找NSObject是否有这个方法。
所以以上面的例子举例,NSObject分类中,实现 -walk 或者 +walk 都能调用,但是 用 +walk类方法的实现,比 -walk实例方法的实现,要少查找一步,可以再次结合下面这张图去仔细体会:

从Runtime源码角度,探究消息传递流程_第3张图片
4989398-31fa4e9295e53805.png

根元类的父类是NSObject NSObject的元类是根元类,这一点挺重要。

我们继续看下类方法的动态方法解析,有什么不同:

void _class_resolveMethod(Class cls, SEL sel, id inst)
{
    if (! cls->isMetaClass()) {
        // try [cls resolveInstanceMethod:sel]
        _class_resolveInstanceMethod(cls, sel, inst);
    } 
    else {
        // 此时会进入到这里 cls 也就是元类
        _class_resolveClassMethod(cls, sel, inst);
        if (!lookUpImpOrNil(cls, sel, inst, 
                            NO/*initialize*/, YES/*cache*/, NO/*resolver*/)) 
        {
// 会走到这一步,这一步会调用NSObject的+resolveInstanceMethod:
// 它会给 cls 发送 SEL_resolveInstanceMethod 这个方法,又是方法查找
// 一直到根元类的父类,NSObject  发送 SEL_resolveInstanceMethod 消息
            _class_resolveInstanceMethod(cls, sel, inst);
        }
    }
}

_class_resolveClassMethod实现:

static void _class_resolveClassMethod(Class cls, SEL sel, id inst)
{
    assert(cls->isMetaClass());

    if (! lookUpImpOrNil(cls, SEL_resolveClassMethod, inst, 
                         NO/*initialize*/, YES/*cache*/, NO/*resolver*/)) 
    {
        // Resolver not implemented.
        return;
    }

    BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
    // 给 _class_getNonMetaClass(cls, inst) 这个对象 发送了消息 ,其实内部也是一个类对象 不是 元类
    bool resolved = msg(_class_getNonMetaClass(cls, inst), 
                        SEL_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(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 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));
        }
    }
}
  • 动态方法解析,防崩溃处理
    可以给NSObject添加分类,重写
    + resolveInstanceMethod:方法,因为不管是任何类的实例方法或者类方法,没有实现的时候,都可以走到NSObject分类中+resolveInstanceMethod: 这个方法中。

  • 备用接受者
    如果动态解析没处理,进入消息转发,这个只有汇编调用,没有源码实现
    如果上面那个没有新增方法,那就问问是否有别人会处理这个方法?
    调用的是:

+ (id)forwardingTargetForSelector:(SEL)aSelector; // 类
- (id)forwardingTargetForSelector:(SEL)aSelector; // 实例

比如一个DDPerson类,其中有-run方法声明,但是没有实现,我们可以指定一个别的接收者:

- (id)forwardingTargetForSelector:(SEL)aSelector{
    NSLog(@"instance - forwardingTargetForSelector");
    DDStudent *student = [DDStudent new];
    if ([student respondsToSelector:aSelector]) {
        return student;
    }
    return self;
}

这样就会调用到DDStudent中的run方法。student能够处理这条消息,所以这条消息被student成功处理,消息转发流程提前结束。

  • 最后的消息转发
    但是如果forwardingTargetForSelector方法中返回的是nil或者self呢?说明没有别的处理者,调用
    - (void)forwardInvocation:(NSInvocation *)anInvocation,
    在调用forwardInvocation:之前会调用
    - (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector方法
    来获取这个选择子的方法签名,然后在
    -(void)forwardInvocation:(NSInvocation *)anInvocation方法
    中你就可以通过anInvocation拿到相应信息做处理,实例代码如下:
- (void)forwardInvocation:(NSInvocation *)anInvocation{
    NSLog(@"forwardInvocation");
    DDStudent *student = [DDStudent new];
    anInvocation.target = student;
    anInvocation.selector = @selector(run);
    [anInvocation invoke];
}

- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
    NSLog(@"methodSignatureForSelector");
    if (aSelector == @selector(run)) {
        return [NSMethodSignature signatureWithObjCTypes:"v@:"];
    }
    return [super methodSignatureForSelector:aSelector];
}

这个消息就会被Student处理掉了。

完整的转发流程如下:


从Runtime源码角度,探究消息传递流程_第4张图片
image.png

那么最后消息未能处理的时候,还会调用到- (void)doesNotRecognizeSelector:(SEL)aSelector这个方法,如果这个方法也没有实现,那么就会抛出没有找到该方法实现的错误了。所以我们也可以在这个方法中做些文章,避免掉crash,但是只建议在线上环境的时候做处理,实际开发过程中还要把异常抛出来。

Runtime的使用场景就不多介绍了,因为用的太多了。

你可能感兴趣的:(从Runtime源码角度,探究消息传递流程)