美文网首页
OC消息机制

OC消息机制

作者: 不爱吃西瓜的桃子 | 来源:发表于2018-11-28 15:52 被阅读0次

    基于Runtime的动态特性

    在苹果的官方文档中,对Runtime的介绍如下:

        The Objective-C language defers as many decisions as it can from compile time and link time to runtime. Whenever possible, it does things dynamically. This means that the language requires not just a compiler, but also a runtime system to execute the compiled code. The runtime system acts as a kind of operating system for the Objective-C language; it’s what makes the language work.    

    ObjC 面向Runtime的语言,它会尽可能地把决策从编译时和连接时推迟到运行时(简单来说,就是编译后的文件不全是机器指令,还有一部分中间代码,在运行的时候,通过Runtime再把需要转换的中间代码在翻译成机器指令)这使得ObjC有着很大的灵活性。比如:

    1、动态的确定类型

    2、我们可以动态的确定消息传递的对象

    3、动态的给对象增加方法的实现 等等

    那么,ObjC是如何实现这一灵活性?

    ObjC Runtime库是开源的,可从这里下载 http://opensource.apple.com/

    基于Runtime的消息传递机制

    ObjC之所以说是面向Runtime的语言,最重要的特征是其消息传递机制。

    什么是消息传递?消息传递不就和C语言的函数调用一个意思么。。

    在C语言中,我们调用函数时,必须先声明函数(或者自上而下),而实际上,声明函数就是获取函数地址,调用函数就是直接跳到地址执行,代码在被编译器解析、优化后,便是成为一堆汇编代码,然后连接各种库,完了生成可执行的代码(即是静态的)。

    在ObjC中,首先要搞清楚为什么不用Function Call 而用 Messaging 呢?一般调用函数(方法),讲的就是object对象,比如说:

    person.say();

    这里表示的就是person对象调用了say函数

    而Messaging则是从Runtime的角度上 比如说

      [receiver doSomething];

    编译后,则是成

       objc_msgsend(receiver @selector(doSomething));

     正确的理解应该是,表示你需要向receiver发送一个消息(doSomething),而此时receiver不一定调用了doSomething这个方法,(ObjC中给任意对象发送任意消息,编译时是可以通过了)只有到了运行时,才会先去receiver是否去响应这个消息,在决策时执行这个方法,还是其他方法,或者转发给其他对象。另外,在ObjC中,可以向nil发送消息任意方法不会Crash

    假设我们有两个类:Human类和Animal类,其中Human类有sayHello方法

    Human *person = [[Human alloc] init];

    Human *xiongmao = [[Animal alloc] init];

    [person sayHello];

    [xiongmao sayHello];

    我们给person和xiongmao都发送了sayHello消息,而实际上,xiongmao这个对象时不响应sayHello这个方法的。而此时编译器却没有给出警告和错误信息,因此,LLVM的Clang编译器在编译时,只是简单的进行语法分析,消息的发送实际上是在运行时才执行的。

    而在运行时,xiongmao这个对象指向的是Animal这个类,因为Animal这个类没有sayHello的执行方法,这个时候,编译器才报错,程序崩溃。

    我们得出的结论是:

    1、消息的发送是在runtime时执行的

    2、编译时,编译器只是简单的进行语法分析,比如对应的类有没有响应的方法(实际上,对象是否是类的实例,编译器此时无法确定)

    在发送以下方法时,都是在运行时动态判断的:

    respondsToSelector:

    isKindOfClass: / isMemberOfClass:

    instancesRespondToSelector:

    conformToProtocol:

    等等

    消息传递的实现机制

    那么,ObjC是如何基于Runtime实现消息传递机制,消息传递机制又是怎样的。

    先来了解第一个概念meta-class

    我们在运行时创建一个NSError的子类并为它添加一个方法:

    Class newClass = objc_allocateClassPair([NSError class],"RuntimeErrorSubclass",0);

    class_addMethod(newClass,@selector(report),(IMP)ReportFunction,"v@:");

    objc_registerClassPair(newClass);

    我们使用了Runtime库中的objc_allocateClassPair为class pair创建了存储空间,而这里的class pair语义上表示一对,这里指的就是类对象和元类。

    我们发现,上面的newClass在创建时,也是用了allocate分配存储空间,这就说明了,类实际上也是个对象。

    什么是Class,在ObjC中,每个Class实际上都有两个Class,the Class object (类对象)和meta Class(元类), the Class object 定义了instance method,metaClass 定义了class method,所以,每个Class对象实际上是metaClass的一个单例。这里的类对象是编译器为每个类生成的有且只有一个的单例。而这个单例的isa指针指的是metaClass。因此,对应类的内存布局的理解是:

    1、每个类实际上都有类对象和元类两个概念

    2、类对象是编译器为每个类生成的有且只有一个的保存关于实例方法的信息的单例

    3、元类则是保存类方法信息的

    4、类对象时元类对象的一个单例 ,类对象的isa指针指向元类

    5、类对象和元类都是基于objc_class结构的

    typedef struct objc_class *Class;

    struct objc_class

    {

        Class isa;

        Class super_class;     //父类

        const char* name;    //类名

        long version;            //版本信息

        long info;                 //类信息

        long instance_size;                                    //实例大小

        struct objc_ivar_list *ivars;                        //实例参数链表

        struct objc_method_list *methodLists;     //方法链表

        struct objc_cache *cache;                         //方法缓存

        struct objc_protocol_list *protocols;        //协议链表

    而NSObject的结构是:

     struct NSObject{

        Class *isa

    }

    在objc中每个实例对象都有objc_class结构体的指针isa指向其类的类对象,而其类的类对象的isa指针指向其类的元类对象,该类的元类对象的isa指针指向NSObject的元类对象。NSObject的元类对象指向其类对象。

    至于super_class就不用说了吧。。

    具体的内存布局,可以参考此文章:http://www.cocoawithlove.com/2010/01/what-is-meta-class-in-objective-c.html

    从ObjC的内存布局可以知道,通过isa指针,对象可以访问它对应的类的信息和相应的父类的信息,而消息机制便是通过isa指针来实现动态特性的。

    上面的方法链表中,里面存储的是Method类型的,这里有Method、SEL、IMP三个概念

    typedef struct objc_method *Method

    typedef struct objc_method{

        SEL method_name;    

        char *method_type;

        IMP method _imp;

    };

    SEL表示方法的签名,一般SEL = @selector();

    IMP 表示函数指针,其定义:

    typedef id(*IMP)(id,SEL,..)

    前面两个是函数指针的对象和方法签名,后面就是函数的参数。 比如

    void(*setName_Func)(id, SEL, NSString*);

    setName_Func = (void(*)(id,SEL,NSString))[receiver methodForSelecor:@selector(setName:)];  //返回name的函数指针

    setName_Func(receiver,@selector(setName:),Liming); //通过函数指针调用函数

    运行时消息的传递机制

    在ObjC中,消息只有在运行时才被绑定到方法的执行中,我们发送消息时,使用的是[],而在运行时,被ObjC的Runtime库编译成

    objc_msgsend(receiver ,selector, arg1, arg2,...) 

    这个函数给动态绑定做了以下工作:

    1、首先,它会根据给定的selector,找到相关的procedure(执行方法),因为ObjC的多态性,所以寻找其procedure是根据receiver的类

    2、call the procedure,并且把相关的arg1,arg2等参数传给它

    3、返回procedure的返回值。

    传递消息的关键点是编译器为每个对象和类所build的结构,这个结构包括:

    1、superclass的指针

    2、类的分派表,这个类的分派表包含了相关类的方法的地址

    这个结构由对象和类的isa指针指向

    (实际上,这里和我们上面讨论的内存布局相似)

    苹果文档关于运行时执行方法绑定的流程概述

     When a message is sent to an object, the messaging function follows the object’s isa pointer to the class

    structure where it looks up the method selector in the dispatch table. If it can’t find the selector there,

    objc_msgSend follows the pointer to the superclass and tries to find the selector in its dispatch table. Successive

    failures cause objc_msgSend to climb the class hierarchy until it reaches the NSObject class. Once it locates

    the selector, the function calls the method entered in the table and passes it the receiving object’s data structure.

    上面讲的是关于消息的绑定机制,而在运行时,还存在消息的转发机制

    前面我们讲过,在ObjC中,发送消息给一个不响应这个方法的对象,是合法的。之所以设计这种机制的原因,就是来模拟多重继承(ObjC中是不支持多重继承的)。转发机制是Runtime非常重要的特性,其机制大概如下:

    1、Runtime首先会进行消息绑定,通过父类的cache和分发表来绑定消息的执行方法

    2、如果没有,运行时会给类的对象发送+(BOOL)resolveInstanceMethod:(SEL)Name消息,这个消息的执行方法允许你在运行时给该类增加执行方法。如果消息返回的是YES,则会redispatch消息(就是对消息在进行绑定)

    3、如果2步骤还不行,Runtime会发送-(id)forwardingTargetForSelector:(SEL)aSelector,这个消息返回另外一个能响应该消息的对象,然后把消息转发给别的对象

    4、如果3步骤返回的对象是nil,或者是self,它会发送- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector ,这里会返回一个方法签名用于Invocation,即-(void)forwardInvocation:(NSInvocation *)anInvocation.通过这个方法,你可以把消息转发给任意拥有相应执行方法的类(其实就是模拟多重继承)

    5、如果4步骤都不行,运行时就会发送消息 - (void)doesNotRecognizeSelector:(SEL)aSelector 给你的对象,执行这个方法将会抛出一个异常,然后程序就崩溃了。。

    原文:https://blog.csdn.net/dan_163/article/details/38268957

    相关文章

      网友评论

          本文标题:OC消息机制

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