什么是运行时呢?从字面意思来看,就是一个程序在其运行的过程当中所作的一些事情。而苹果在 object—C 中提供了一套纯 c 语言的 api,这套 api 即为 runtime。objective-c
在 iOS 开发的过程当中,正式由于runtime 的特性,让 object-C 具备了吸引人的魅力。使得咱们能够真正作到玩语言,作出高逼格的花样,快乐就完了~api
要了解运行时,咱们得先了解 object-C 的消息机制,能够看下面的流程:架构
一、编译器会先将代码 [obj doSomeThing] 转化为 objc_msgSend(obj, @selector (doSomeThing)) 函数去执行。函数
二、在 objc_msgSend() 函数中,首先经过 obj 的 isa 指针找到 obj 对应的 class。架构设计
三、在 class 中会先去 cache 中 经过 SEL 查找对应函数 doSomeThing(cache 中method 列表是以 SEL 为 key 经过 hash 表来存储的,这样能提升函数查找速度),若 cache 中未找到。再去 class 中的消息列表 methodList 中查找,若 methodList 中未找到,则取 superClass 中查找。若能找到,则将 doSomeThing 加入到 cache中,以方便下次查找,并经过 method 中的函数指针跳转到对应的函数中去执行。debug
看完上面的流程,可能会迷惑里面提到的一下字端是什么意思,那咱们来看一下一个 OC 类中都包含了什么?设计
能够先看类结构体 struct objc_class。虽然该结构体中有许多变量,可是从变量名中咱们能够大概理解其含义,结构体里包含了 指像父类的指针、类名、版本等等信息,里面有些变量会在下面的应用中进行用到。3d
下面就让咱们来看看 runtime 的具体应用吧~指针
Runtime 的功能很是强大,这也是魅力所在,它可以在程序运行的时候,获取一个类中的全部信息,而且可以根据开发者意愿去修改。脑洞多大,变化就能多大~code
接下来,本文主要就下面几点进行讲解:
(1)使用 class_addMethod 函数在运行时对函数进行动态增长新函数
(2)消息转发
(3)使用 class_copyPropertyList 及 property_getName 获取类的属性列表及每一个属性的名称
(4) 使用 class_copyMethodList 获取类的全部方法列表
(5) Method Swizzling
首先来看一下 API 文档解释
先看下函数中的各个参数的含义:
Class _Nullable cls:传入的是一个类,也就是你想要在哪一个类里进行添加方法
SEL _Nollnull name:想要添加的方法名字
IMP _Nonnull imp:imp 是 Implement 缩写,表示指向方法的实现地址
const char * _Nonnull types:对于参数与返回值的描述。举个例子:"v@:",表示的是返回值为 void 而且没有参数。
下面看一下具体的调用:
这里要给 HJXPerson 类动态添加一个 sayHello 的方法。首先要拿到添加的方法 sayHello 的 IMP 指针,而后调用相应的接口去添加方法。
(注:当要执行动态添加方法的时候,须要用 performSelector 来进行调用,由于 performSelector 是运行时系统负责去找方法的,在编译时候不作任何校验;若是直接调用编译是会自动校验)
文章开头介绍了 Objective-C 中调用一个方法的流程,但若是最后都没有找到对应方法,咱们的程序都会 crash 并抛出信息没有找到对应的方法。其实在 crash 以前还会进行一套流程,那就是消息转发。转发流程图以下:
下面将分别结合代码事例进行介绍这几种方法的处理:
此为消息转发的先导,也叫动态决议机制。见下面例子:
在 HJXPerson 类中并无声明 eat 这个函数,因此在实例调用方法的时候会进入到这个回调之中。
其实,objective-c 的方法就是至少带有两个参数(self 和 cmd)的普通的 C 函数。所以在代码中提供这样一个 C 函数 dynamicMethodIMPEat,让它来充当对象方法 eat 这个 selector 的动态实现。
由于 eat 是被对象所调用,因此它被认为是一个对象方法,于是应该在 resolveInstanceMethod 方法中为其提供实现。
这个方法只能让咱们把消息转发到另外一个能处理这个消息的对象,可是没法处理消息的内容,好比参数和返回值。例子以下:
该类为 HJXCat 类,里面声明了 “useTool” 这个方法但并无实现,因此在未添加 resolveInstanceMethod
处理的时候,会进入到 forwardingTargetForSelector 方法中,而后能够根据传入的 aSelector,来转交给其余类去处理相应的方法。
(注:返回的为想要把方法交给的类的一个实例对象)
先看下工程代码
forwardInvocation 的总体流程和 forwardingTargetForSelector 基本上是差很少的。经过 aSelector 来进行判断是否要进行转发,而后进行手动签名。而后在 anInvocation 中能够获取到函数传里过程当中全部信息。
forwardingTargetForSelector 和 forwardInvocation 区别:
快速转发:forwardingTargetForSelector 仅支持一个对象的返回,也就是说消息只能被转发给一个对象、没法处理消息的内容,好比参数和返回值。
普通转发:forwardInvocation 能够将消息同时转发给任意多个对象。
首先看是否为该 selector 提供了动态方法决议机制,若是提供了则转到 2;若是没有提供则转到 3;
若是动态方法决议真正为该 selector 提供了实现,那么就调用该实现,完成消息发送流程,消息转发就不会进行了;若是没有提供,则转到 3;
其次看是否为该 selector 提供了消息转发机制,若是提供了消息了则进行消息转发,此时,不管消息转发是怎样实现的,程序均不会 crash。(由于消息调用的控制权彻底交给消息转发机制处理,即便消息转发并无作任何事情,运行也不会有错误,编译器更不会有错误提示。);若是没提供消息转发机制,则转到 4;
运行报错:没法识别的 selector,程序 crash
这两个函数总从字面意思上,能够看出分别是获取一个类中的全部属性名称及方法名。这两个方法能够算是基础,让你能够知道一个类内的全部属性及方法,以便你接下来能够为所欲为的对于其修改,修改哪里。
下面为调用方法来获取 HJXCat 中的属性及方法:
(上面的各个属性都是声明在 .m 文件中的)
首先让咱们来看下方法交换的原理:
在 Objective-C 中调用一个方法,实际上是向一个对象发送消息,查找消息的惟一依据是 selector 的名字。利用 Objective-C 的动态特性,能够实如今运行时偷换 selector 对应的方法实现,达到给方法挂钩的目的。
每一个类都有一个方法列表,存放着 selector 的名字和方法实现的映射关系。IMP 有点相似函数指针,指向具体的 Method 实现。
归根结底,都是偷换了 selector 的 IMP
跟消息转发相比,Method Swizzling 的作法更为隐蔽,甚至有些冒险,也增大了debug的难度。
下面咱们来看一下代码部分:
就这么简简单单几行,就可以实现方法的交换。原有的 eat 方法在调用 hasEatenFull 方法后,就与 play 方法进行了交换,再次执行 eat 方法,其实就是调用了 play 方法。
虽然 Swizzling 能够任你喜欢的去弄,想要玩起来,anyway,均可以。可是开发工程中,咱们须要先好好的去想想,能不能利用良好的代码和架构设计来实现,或者是深刻语言的特性来实现。好用,可是不能滥用。
Runtime 在代码运行的时候,最大提供给咱们操做性,能够帮助咱们理解代码的运行,窥探咱们看不到的代码,扩展更多更有趣的功能。
代码玩起来,快乐就完啦~用好,不滥用~
Article by 夏风_Me