美文网首页
Runtime 的理解和应用

Runtime 的理解和应用

作者: 你的代码掉了 | 来源:发表于2019-04-18 15:37 被阅读0次

    一,怎么理解oc是动态语言,Runtime 又是什么?

    静态语言: 如c语言,编译阶段就要决定调用那个函数,如果函数没有实现 就会编译报错.

    动态语言:如oc语言,便于阶段并不能决定真正调用那个函数,只要声明过即可使用.没有实现也不会报错.

    我们常说oc是一门动态语言,就是因为它总是把一些决定性的工作从编译阶段推迟到运行时阶段,oc代码的 运行不仅需要编译器,还需要运行时的系统(Runtime system)来执行编译后的代码.   

    Runtime 是一套底层纯C语言API,OC代码最终都会被编译器转化为运行时的代码,通过消息机制决定函数调用的方式,这也是   OC 作为动态语言的使用基础.

    二,理解消息机制的基本原理.

    OC的方法调用都是类似[receiver selector]的形式,其实每次都是消息发送的过程.

    第一步:编译阶段

    [receiver selector] 被编译器转化 分为两种情况: 有参数,无参数.

    1.不带参数的被编译为:objc_msgSender(receiver,selector)

    2.带参数的被编译为:objc_msgSender(receiver,selector,org1,org2,....)

    第二步:运行时阶段

    消息接收者recever 寻找对应的selector,也分为两种情况:

    1.接收者能够找到对应的selector,直接执行selector方法.

    2.接受者找不到对应的selector,消息被转发或者是临时向这个selector 添加实现内容,否则崩溃.

    说明:   OC调用方法[receiver selector] 编译阶段确定了要向那个接受者发送message 消息.但是接收者如何响应是在运行时决定的.

    三,与Runtime的交互

    NSObject 方法

    Runtime 最大的特性就是实现了OC语言的动态加载,作为大部分类的根类NSObject,其本身就具有了一些非常具有运行时动态特性的方法,例如:respondsToSelector:该方法能够检查代码在运行阶段当前的对象是否能相应指定的消息. 类似的方法还有:

    - description://返回当前类的描述信息;

    - class //返回对象的类

    - iskingOfclass://属于某一个类

    -methodForSelector //返回指定方法的实现的地址

    使用Runtime 函数,分析Runtime 中的数据结构

    oc代码被编译器转化为C语言,然后通过运行时执行,最总实现了动态调用,这其中oc的类,对象,和方法等都可以在Runtime 源码中找到他们的定义.

    那么,我们来看下:

    #import<objc/runtime.h>

    #import<objc/message.h>

    然后,我们需要使用组合键"Command +鼠标点击",即可进入Runtime的源码文件,下面我们继续来一一分析OC代码在C中对应的结构。

    1.id ->objc_object

    id 是一个指向objc_object结构体的指针,即 typedef struct objc_object *id;

    下面是Runtime 中对obj_object的具体定义:

    struct objc_object{

    Class_Nonnull isa OBJC_ISA_AVALIABILITY;

    };

    我们都知道id在oc中是表示一个任意类型的实例,从这里也可以看出来,oc中的对象虽然没有明显的使用指针,但是在oc代码被编译器转化为c语之后,每个oc 对象其实都是拥有一个isa指针的.

    Class ->objc_class

    class 是一个指向objc_class结构体的指针,即在Runtime中:

    struct objc_class *Class; 定义略过

    isa指针:

    我们会发现objc_class和objc_object同样是结构体,而且都拥有一个isa指针.我们很容易理解objc_object的isa指针指向对象的定义,那么objc_class的指针是怎么回事.?其实,在Runtime中objc类本身同时也是一个对象,Runtime把类对象所属类型叫做元类.用于描述类对象本身所具有的特征,最常见的类方法就被定义与此.所以objc_class中的isa指针指向的是元类. 每个类仅有一个类对象,而每个类对象仅有一个与之相关的元类.

    super_class 指针:

    super_class指针指向objc_class指针指向objc_class类所继承的父类,但是如果当前类已经是最顶层的类,则super_class指针为NULL.

    cache:

    为了优化性能,objc_class中的cache结构体用于记录每次使用类或则对象调用的方法,这样每次响应消息的时候,runtime系统会优先在cache中寻找响应的方法,相比直接在类的方法列表中遍历查找,xiao率更高.

    ivars:

    ivars:

    ivars用于存放所有成员变量对的属性信息.属性的存取方法都存放在methodlists中.

    methodLists 用于存放对象的所有成员方法.

    SEL

    SEL 是一个指向objc_selector结构体的指针,SEL在OC中被称作方法选择器,用于表示运行时方法的名字,在编译的时候,会依据每一个方法的名字,参数序列,生成一个唯一的整形标识,这个标识就是SEL.

    注意:

    1.不同类中相同名字的方法对应的方法选择器是相同的.

    2.即使是同一个类中,方法名字相同而变量类型不同也会导致他们具有相同的方法选择器.

    Ivar 代表类中实例变量的类型,是一个指向object_ivar结构体的指针,

    Method 表示某个方法的类型,其里面的 的参数定义为:

    method_name:方法名字

    method_types:一个char指针,指向储存方法的参数类型和返回值类型.

    method_imp:本质上是一个指针,指向方法的实现,

    IMP 是一个函数指针,它指向了方法实现的首地址,当oc发起消息后,最终执行的代码是有IMP指针决定,利用这个特性,我们可以对代码进行优化,当需要大量重复调用方法的时候,我们可以绕开消息的绑定直接利用IMP  指针调起方法,这样的执行会更加高效.

    深入理解Runtime 消息发送

    OC调用方法被编译转化为如下的形式:

    id_NULLable objc_msgSend(id_NULLable self,SEL _Nonnull op,...)

    运行时阶段的消息发送的详细步骤如下:

    1.检测selector 是不是需要忽略,比如Mac OS X 开发,有了垃圾回收机制,就不理会retain release 这些函数了.

    2.检测target 是不是nil 对象,OBJC的特性是允许对一个 nil 对象执行任何一个方法不会crash,因为会被忽略.

    3.如果上面两个都过了,那么就开始查找这个类的IMP,先从cache里面找,若可以找到就跳到对应的函数去执行.

    4.如果class 里面找不到就找一下方法列表,method_Lists.

    5.如果method_Lists找不到,就到超类的方法列表里寻找,一直找,直到找到NSObject类为止.

    6.如果还是找不到,Runtime就会提供如下的三中方法来处理:动态方法解析,消息接受者重定向,消息重定向.这三种方法的调用关系如下:

    消息转发流程图

    1.动态方法解析(Dynamic Method Resolution)

    所谓的动态方法解析,我们可以理解为通过cache和方法列表没找到方法时,Runtime 为我们提供的一次动态添加方法实现的机会,主要有一下方法:

    +(BOOL)resolveClassMethod:(SEL)sel;//类方法没找到时,可用此方法实现

    +(BOOL)resolveInstanceMerhod:(SEL)sel//实例方法没找到时调用

    下面举个栗子:

    类方法动态添加方法 实例方法动态添加实现方法

    "v@"具体可以参考本链接.

    消息接受者重定向:

    我们注意到动态方法解析过程中的两个resolve方法都返回了布尔值,当他们返回YES(动态方法解析成功)时方法即刻正常运行,但是他们如果返回NO,消息发送机制就进入了消息转发(消息接受者重定向)的阶段了.我们可以使用Runtime 通过下面的犯方法替换消息接受者的为其他对象,杏儿保证的继续执行.

    - (id)forwardingTargetForSelector:(SEL)aselector// 重定向类方法的消息接受者,返回一个类

    -(id)forwardingTargetForSelector:(SEL)aselector//重定向一个实例方法的接受者,返回一个实例对象.

    举个荔枝如下图:

    类方法 重定向 实例方法重定向

    消息重定向

    如果动态解析,和消息接受者重定向返回的都是NO ,系统会通过Runtime 的消息转发机制,进行最后一次的IMP 寻找.被称为消息重定向

    - (void)forwardInvcation:(NSInvocation *)anInvocation;

    其实每个对象都从NSobject类中继承了forwardInvocation:方法 但是 NSObject中的只是简单地调用了doesNotRecongnizerSelector:方法  来提示 错误. 所以我们 可以重写这个方法,对不能处理的消息做一些默认处理,也可以将消息转发给其他对象来处理.而不是抛出错误.

    我们注意到anInvocation 是 forwardInvocation:的唯一参数,它封装了原始的消息和消息参数.正事因为它,我们还不得不重写另一个函数.methodSignatureForSelector. 这是因为在forwardInvocation:消息发送前,Runtime 会向对象发送methodSignatureForSelector消息,并取到的方法签名用于生成NSInvocation对象.

    举个荔枝:

    消息重定向

    1.从以上的代码中就可以看出,forwardingTargetForSelector仅支持一个对象的返回,也就是说消息只能被转发给一个对象,而forwardInvocation可以将消息同时转发给任意多个对象,这就是两者的最大区别。

    2.虽然理论上可以重载doesNotRecognizeSelector函数实现保证不抛出异常(不调用super实现),但是苹果文档着重提出“一定不能让这个函数就这么结束掉,必须抛出异常”。(If you override this method, you must call super or raise an invalidArgumentException exception at the end of your implementation. In other words, this method must not return normally; it must always result in an exception being thrown.)

    3.forwardInvocation甚至能够修改消息的内容,用于实现更加强大的功能。

    多继承的实现思路:Runtime


    多继承

    参考文章:Runtime-iOS运行时基础篇

    相关文章

      网友评论

          本文标题:Runtime 的理解和应用

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