虽然swift 慢慢会越用越多,但凭借oc 强大的runtime机制,oc依旧是现在的中流砥柱.只做记录探讨 加深自己的理解.runTime简称运行时。就是系统在运行的时候的一些机制,其中最主要的是消息机制。对于C语言,函数的调用在编译的时候会决定调用哪个函数( C语言的函数调用请看这里 )。编译完成之后直接顺序执行,无任何二义性。OC的函数调用成为消息发送。属于动态调用过程。在编译的时候并不能决定真正调用哪个函数(事实证明,在编 译阶段,OC可以调用任何函数,即使这个函数并未实现,只要申明过就不会报错。而C语言在编译阶段就会报错)。只有在真正运行的时候才会根据函数的名称找 到对应的函数来调用。
那OC是怎么实现动态调用的呢?下面我们来看看OC通过发送消息来达到动态调用的秘密。假如在OC中写了这样的一个代码:
[obj xMethod]
其中obj是一个对象,xMethod是一个函数名称。对于这样一个简单的调用。在编译时RunTime会将上述代码转化成
objc_msgSend(obj,@selrctor(xMethod));
首先我们先看下obj这个对象,ios中的obj都继承于NSObject
@interface NSObject <nsobject>{
Class isa OBJC_ISA_AVAILABILITY;
}</nsobjct>
在NSObject中存在一个Class的isa指针。然后我们看看Class:
typedef struct objc_class *Class;
struct objc_class {
Class isa OBJC_ISA_AVAILABILITY;//指向metaclass
#if !__OBJC2__
Class super_class OBJC2_UNAVAILABLE;
//指向父类
const char *name OBJC2_UNAVAILABLE;
//类名
long version OBJC2_UNAVAILABLE;
//类的版本信息,初始化默认为0,可通过runtime函数class_setVersion和class_getVersion进行修改、读取
long info OBJC2_UNAVAILABLE;
//一些标识信息,如CLS_CLASS(0x1L)表示该类为普通class,其中包含对象方法和成员变量;CLS_META(0x2L)表示该类为metaclass,其中包含类方法;
long instance_size OBJC2_UNAVAILABLE;
//该类的实例变量大小(包含从父类继承下来的实例变量)
struct objc_ivar_list *ivars OBJC2_UNAVAILABLE;
//用于存储每个成员变量的地址
struct objc_method_list **methodLists OBJC2_UNAVAILABLE;
//与 info 的一些标志位有关,如CLS_CLASS (0x1L),则存储对象方法,如CLS_META (0x2L),则存储类方法;
struct objc_cache *cache OBJC2_UNAVAILABLE;
//指向最近使用的方法的指针,用于提升效率
struct objc_protocol_list *protocols OBJC2_UNAVAILABLE;
//存储该类遵守的协议.
#endif
} OBJC2_UNAVAILABLE;
我们可以看到,对于一个Class类中,存在很多东西,下面来一一解释下
Class isa:指向metaclass,也就是说静态的Class.一般一个obj对象中的isa会指向普通的Class,这个Class中存储普通成员变量和对象方法("-"开头的方法),普通Class中的isa指针指向静态Class,静态Class中存储static类型成员变量和类方法("+"开头的方法)。
Class super_class:指向父类,如果这个类是根类,则为NULL。
下面的一张图片很好的描述了类和对象的关系
注意:所有metaclass中isa指针都指向根metaclass.而根metaclass则指向自身。Root metaclass是通过继承Root class产生的。与root class结构体成员一致,也就是前面提到的结构。不同的是Root metaclass的isa指针指向自身。
Class类中其他的成员这里就先不做过多解释了,下面我们看看
@selector(xMethod):这是一个SEL方法选择器。SEL其主要是快速的通过方法名字(xMethod)查找到对应方法的函数指针,然后调用其函数。SEL其本身是一个int类型的一个地址,地址中存放着方法的名字。对于一个类中。每一个方法对应着一个SEL。所以iOS类中不能存在2个名称相同 的方法,即使参数类型不同,因为SEL是根据方法名字生成的,相同的方法名称只能对应一个SEL。
下面我们就来看看具体消息发送之后是怎么来动态查找对应的方法的。
首先,编译器将代码[obj xMethod] 转化为[objc_msgSend(obj,@selector(xMethod))];在objc_msgSend函数中。首先通过obj的isa指针找到obj对应的class。在class中先去cache中通过SEL查找对应函数method,若cache中未找到。再去methodList中查找,若methodist中未找到,则取superclass中查找。若能找到,则将method加入到cache中,以方便下次查找,并通过method中的函数指针跳转到对应的函数中去执行。