美文网首页
Runtime-消息三步处理机制

Runtime-消息三步处理机制

作者: coder_feng | 来源:发表于2019-06-16 22:30 被阅读0次

    Runtime 方法调用本质

    OC是一门runtime语言,OC调用方法的实际,其实就是消息转发,我们可以通过底层C++源码来探索方法调用的本质:xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m

    举个例子:

    OC:[person test];

    C++:((void(*)(id, SEL))(void*)objc_msgSend)((id)person, sel_registerName("test"));

    通过这段源码可以看出c++底层源码中方法调用最终都是通过objc_msgSend函数,这种模式在OC领域中叫做消息机制,表示给方法的调用者发送消息,上面中person代表消息接受者;test代表消息名称

    消息几个过程

    消息发送阶段:负责从类或者父类的缓存列表或者baseMethodList方法列表中查找方法;

    动态解析阶段:如果消息发送阶段没有找到方法,则会进入动态解析阶段,负责动态的添加方法实现

    消息转发阶段:如果消息发送阶段没有找到方法,动态解析又没有添加对应的方法,则会进行消息转发阶段,将消息转发给可以处理消息的接受者来处理。

    如果前面这几个过程都没有实现到的话,那么就会爆出一个经典的错误方法:unrecognzied selector sent to instance,接下来我们来细看这几个步骤:

    消息发送阶段

    在runtime源码中搜索_objc_msgSend查看其源码内部实现,在objc-msg-arm64.s汇编文件中可以查看到_objc_msgSend函数的实现流程图和源码图:

    _objc_msgSend流程图 _objc_msgSend函数

    上述汇编源码中会首先判断消息接受者x0,是否为nil,如果为nil则会执行LNilOrTagged,LNiOrTagged内部会执行LReturnZero(内部直接return 0);

    如果传入的消息接受者不为nil,这直接往下执行,CacheLookUp,CacheLookUp内部会对方法列表进行查找,如果找到则执行CacheHit,进而调用方法,否则执行CheckMiss,CheckMiss内部调用__objc_msgSend_uncached

    CacheLoopup

    可以看到CacheLookup内部方法缓存列表进行查找,如果找到则执行CacheHit,进而调用方法,否则就会执行CheckMiss,CheckMiss内部调用__objc_msgSend_uncached.

    __objc_msgSend_uncached

    __objc_msgSend_uncached 内部会执行MethodTableLookup也就是方法列表查找

    __class_lookupMethodAndLoadCache3

    __class_lookupMethodAndLoadCache3 是汇编语言,而对于方法_class_lookupMethodAndLoadCache3在汇编中会生成__class_lookupMethodAndLoadCache3,所以这个时候我们发现我们能在C++源码中找到这个方法的实现_class_lookupMethodAndLoadCache3

    部分源码图

    具体全部源码可以直接找源码来看,这里不一一描述,下面我将通过一张图来说明一下这个流程先:

    _class_lookMethodAndLoadCache3内部流程

    总结图:

    总结图

    动态解析阶段

    接上面流程图的红色部分继续寻找下去,该部分的源码是:

    _class_resolveMethod动态解析入口 _class_resolveMethod动态内部实现

    从内部代码中实现可以看到动态解析方法之后,会将triedResolver = YES;那么下次就不会在进行动态解析阶段了,虽然会重新执行retry,会重新对方法查找一遍,但是之后无论我们有没有动态解析成功,都不会再次进行动态解析。

    动态解析方法的实现和验证

    动态解析对象方法时,会调用+(BOOL)resolveInstanceMethod:(SEL)sel方法。

    动态解析类方法时,会调用+(BOOL)resolveClassMethod:(SEL)sel方法。

    Person 类动态解析 动态解析测试

    上面的代码中可以看出,person在调用方法test时候,test其实在person.m中并没有实现,但是可以看到[person test]之后并没有爆出错误,反而能执行成功,通过上面对消息发送我们知道,当本类和父类cache和class_rw_t中都找不到方法时,就会进行动态解析的方法,也就是说会自动调用类的resolveInstanceMethod:方法进行动态查找,因此当我们手动执行resolveInstanceMethod的时候,可以按照我们的逻辑自己来实现,从上面中可以看到我们resolveInstanceMethod方法可以看到内部使用到了class_addMethod,并且用other方法类替代test方法的实现,接下来看看class_addMethod函数具体实现

    class_addMethod 函数

    class_addMethod(__unsafe_unretainedClass cls, SEL name, IMP imp,constchar*types)

    第一个参数:cls给那个类添加方法

    第二个参数:SEL name添加方法的名称

    第三个参数:IMP imp方法的实现

    第四个参数:types方法类型

    另外还有一个class_getInstanceMethod方法获取Method,这个Method其实是一个objc_method类型结构体,其实也可以理解为内部结构同method_t结构体相同,method_t是代表方法的结构体,其内部包含SEL、type、IMP,我们通过自定义method_t结构体,将objc_method强转为method_t查看方法是否能够动态添加成功

    objc_method强转为method_t

    从终端打印信息可以看出,我们的猜测是对的

    不依赖method_getImplementation函数和method_getTypeEncoding函数获取方法的imp和type,手动书写,如下图所示:

    手动书写

    动态解析类方法

    类方法动态解析

    无论我们是否实现了动态解析的方法,系统内部都会执行retry对方法再次进行查找,那么如果我们实现了动态解析方法,此时就会顺利查找到方法,进而返回imp对方法进行调用。如果我们没有实现动态解析方法。就会进行消息转发。

    动态解析流程图:

    动态解析方法流程图

    消息转发阶段

    从源码中可以看出,如果我们经过消息发送阶段找不到,然后到消息动态解析,都还没有找到消息实现者的话,就会信息消息转发阶段,会调用_objc_msgForward_imcache函数,发现在C++源码中找不到_objc_msgForward_imcache函数,但是我们可以在汇编中找到__objc_msgForward_impcache函数中找到__objc_msgForward进而找到__objc_forward_handler

    __objc_msgFoward_impcache __objc_forward_handler

    从这里可以看到这个方法仅仅只是一个错误的信息输出,因为消息转发机制是不开源的,但是还是有一个消息转发的过程,那应该这个消息转发有可能是返回了某种类型的对象,该对象实现相应的方法。通过代码来实现一下:

    forwardingTargetForSelector

    从上述例子中可以看出,当本类没有实现方法,并且没有动态解析方法,就会调用forwardingTargetForSelector函数,进行消息转发,因此我们可以实现forwardingTargetForSelector函数,在其内部将消息转发给可以实现此方法的对象,如果forwardingTargetForSelector函数返回nil,或者没有实现的话,就会调用methodSignatureForSelector方法,用来返回一个方法签名,这个是消息机制中最后的机会了,如果methodSignatureForSelector方法返回正确的方法签名就会调用forwardInvocation方法,forwardInvocation方法内提供一个NSInvocation类型的参数,NSInvocation封装了一个方法的调用,包括方法的调用者,方法名,以及方法的参数,因此我们可以在forwardInvocation函数内修改方法调用对象就可以了,最后如果methodSignatureForSelector返回的为nil,就会来到doseNotRecognizeSelector:方法内部,程序crash提示无法识别选择器unrecognized selector sent to instance;

    验证一下:将上面的代码更改一下,

    消息转发最后机制

    流程图,接上面的消息转发机制:

    消息转发阶段

    详解NSInvocation

    methodSignatureForSelector 方法中返回的方法签名,在forwardInvocation中被包装成NSInvocation对象,NSInvocation提供了获取和修改方法名,参数,返回值等方法,简单来说,在forwardInvoation函数中我们可以做绝对的修改;

    举例说明一下,现在我们为上面的driving方法添加返回值和参数,并在forwardInvocation方法中修改方法的返回值及参数。

    nsinvocation

    从上述打印结果可以看出forwardInvocation方法中可以对方法的参数及返回值进行修改。

    类方法的消息转发

    类方法消息转发同对象方法一样,同样需要经过消息发送,动态方法解析之后才会进行消息转发机制。我们知道类方法是存储在元类对象中的,元类对象本来也是一种特殊的类对象。需要注意的是,类方法的消息接受者变为类对象。

    当类对象进行消息转发时,对调用相应的+号的forwardingTargetForSelector、methodSignatureForSelector、forwardInvocation方法,需要注意的是+号方法仅仅没有提示,而不是系统不会对类方法进行消息转发。

    总结消息处理机制

    OC中的方法调用其实都是转成了objc_msgSend函数的调用,给receiver(方法调用者)发送了一条消息(selector方法名)。方法调用过程中也就是objc_msgSend底层实现分为三个阶段:消息发送、动态方法解析、消息转发,而这三个具体的实现和流程上面都说得很清楚了哈,有漏或者错误的地方,阅读者可以提出留言哈

    相关文章

      网友评论

          本文标题:Runtime-消息三步处理机制

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