清晰理解Objective-C元类

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/zengxu2011/article/details/50164353

看了几篇别的博客讲得绕来绕去,让人看了之后感觉似懂非懂,这里总结一下。如有不当之处请读者指出。

元类是什么

众所周知Objective-C(以下简称OC)中的消息机制。消息的接收者可以是一个对象,也可以是一个类。那么这两种情况要是统一为一种情况不是更方便吗?苹果当然早就想到了,这也正是元类的用处。苹果统一把消息接收者作为对象。等等,这是说,类也是对象?yes,就是这样。就是说,OC中所有的类都一种对象。由一个类实例化来的对象叫实例对象,这好理解,那么,类作为对象(称之为类对象),又是什么类的对象?当然也容易猜到,就是今天的主题——元类(Metaclass)。现在到给元类下定义的时候了:元类就是类对象所属的类。所以,实例对象是类的实例,类作为对象又是元类的实例。已经说了,OC中所有的类都一种对象,所以元类也是对象,那么元类是什么的实例呢?答曰:根元类,根元类是其自身的实例(这句话看了下一小节之后就会理解)。

上面讲到了实例对象、类对象、元类对象,有什么区别?

实例对象:当我们在代码中new一个实例对象时,拷贝了实例所属的类的成员变量,但不拷贝类定义的方法。调用实例方法时,根据实例的isa指针去寻找方法对应的函数指针。

类对象:是一个功能完整的对象。特殊之处在于它们是由程序员定义而在运行时由编译器创建的,它没有自己的实例变量(这里区别于类的成员变量,他们是属于实例对象的,而不是属于类对象的,类方法是属于类对象自己的),但类对象中存着成员变量与实例方法列表。

元类对象:OC 的类方法是使用元类的根本原因,因为其中存储着对应的类对象调用的方法即类方法。其他时候都倾向于隐藏元类,因此真实世界没有人发送消息给元类对象。元类的定义和创建看起来都是编译器自动完成的,无需人为干涉。要获取一个类的元类,可使用如下定义的函数:

Class objc_getMetaClass(const char* name); //name为类的名字

此外还有一个获取对象所属的类的函数:

Class object_getClass(id obj) ;

由于类对象是元类的实例,所以当传入的参数为类名时,返回的就是指向该类所属的元类的指针。

元类的构建机制

现在有没怀疑上面第一句红体的话?既然所有类都是对象,那么元类又是什么类的对象呢?这样下去,不是子子孙孙无穷尽也?当然不行,OC作为一门编程语言,当然要满足完备性——既定的各条规则都要满足,不能相互矛盾,也不能存在漏洞。

OC作为运行时语言,上面提到的类与元类在运行时都是objc_class类型。在Objective-C2.0中,objc_class的定义如下:

struct objc_class {
  Class isa;
  Class super_class;
  struct objc_class;
  struct objc_class* ;
  struct objc_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;
}

其中,Class定义如下:

typedef struct objc_class *Class;

可以看出,OC中每个类中都包含一个isa变量,显然这里的isa是指向另一个类的指针,说白了就是表明这个类是哪个类的实例,以便找到代码中调用的本类或父类的类方法(下一节详解)。对于NSObject及其子类,指向的就是它的元类,正如实例中也有个isa指针指向其所属的类一样(下文有讲)。而对于元类,每个元类的isa都指向根元类。那么根元类的isa指向哪里?——它自己。这样就构成一个封闭的循环,实现了无懈可击的OC类系统。这种关系在下面的图中有清晰的体现。

除了isa声明了实例与所属类的关系,还有super_lass表明了类、元类的继承关系——

每个类对象都有对应的元类,每个类(根类除外)都有一个superclass,同样每个元类也有一个superclass,并且子类与子元类、父类与父元类分别在同一层次。这种关系借用网上的一张图来说明,一目了然:

这里写图片描述

注意:根元类的superclass不是nil而是根类。对于OC原生的类,根元类的父类就是系统的根类NSOject。但根类不一定是NSObject,因为后面介绍的objc_allocateClassPair函数也可创建出一个根类。

元类的运行机制

上面讲到了OC运行时类的定义,这里再看对象的定义:

struct objc_object{
  Class isa;
}

这说明OC中每个对象也都包含一个isa变量。这里的isa,指向实例对象所属的类。在运行时,[obj aMessage];被转化为objc_msgSend(obj, @selector (aMessage));,这里,@selector (aMessage)返回一个SEL数据类型,即方法选择器。SEL主要作用是快速的通过方法名字(aMessage)查找到对应方法的函数指针,然后调用其函数。SEL其本身是一个int型的地址,地址中存放着方法的名字。在一个类中,每一个方法对应着一个SEL。iOS类中不能存在两个名称相同的方法,即使参数类型不同,因为SEL是根据方法名字生成的,相同的方法名称只能对应一个SEL。

当一个消息发送给任何一个对象, 方法的检查 从对象的 isa 指针开始,然后是父类。具体地,在objc_msgSend函数中,首先通过obj的isa指针找到obj对应的class。在class中,有一块最近调用的方法的指针缓存,所以先去cache通过selector查找对应的method,若cache中未找到,再去method list中查找,若method list中未找到,则去superClass中查找。若能找到,则将method加入到cache中,以方便下次查找,并通过method中的函数指针跳转到对应的函数中去执行。对最后一句不了解的话,请看objc_method定义:

struct objc_method {
  SEL method_name; // 方法名称
  charchar *method_typesE; // 参数和返回类型的描述字串
  IMP method_imp; // 方法的具体的实现的指针
}

由上看出,OC中实例方法是通过isa找到object所属的class,再在class中找到要调用的method。此可得出结论:class即类对象中存储着实例方法。实际上,类对象中存储着类定义的一切:成员变量、属性列表,遵守的协议等,但不包括类方法。类方法存在哪?答案又是今天的主角——类方法是存在元类中的,此外元类中还存有类的信息(类的版本,名字)。比如发送一个类消息[class aMessage];,class中的isa就指向class的元类,在元类中搜索调用的类方法,搜索层次类似于实例方法的搜索。

元类的应用

类对象和元类对象的相关方法:

①object_getClass跟随实例的isa指针,返回此实例所属的类,对于实例对象(instance)返回的是类(class),对于类(class)则返回的是元类(metaclass),
②-class方法对于实例对象(instance)会返回类(class),但对于类(class)则不会返回元类(metaclass),而只会返回类本身,即[@”instance” class]返回的是__NSCFConstantString,而[NSString class]返回的是NSString。
③class_isMetaClass可判断某类是否为元类.

④使用objc_allocateClassPair可在运行时创建新的类与元类对,使用class_addMethod和class_addIvar可向类中增加方法和实例变量,最后使用objc_registerClassPair注册后,就可以使用此类了。这体现了OC作为运行时语言的强大之一:在代码运行中动态创建类并添加方法。

eg:

Class newClass = objc_allocateClassPair([NSError class], "RuntimeErrorSubclass", 0);
class_addMethod(newClass, @selector(addedMethod), (IMP)added_method_implentation, "v@:"); 
objc_registerClassPair(newClass);

void added_method_implentation(id self, SEL _cmd) 
{  
  //do something
}

说明:objc_allocateClassPair函数的作用是创建一个新类newClass及其元类,三个参数依次为newClass的父类,newClass的名称,第三个参数通常为0。然后可向newClass中添加变量及方法,注意若要添加类方法,需用objc_getClass(newClass)获取元类,然后向元类中添加类方法。接下来必须把newClass注册到运行时系统,否则系统是不能识别这个类的。

收工!

阅读更多
换一批

没有更多推荐了,返回首页