先看一张图,描述的是关于父类和isa指针的指向。再介绍元类之前,首先要清楚几个概念:
- 每个对象都是具体一个类的实例对象,对象的isa指向它的类。比如说创建一个
NSObject *instance = [NSObject new]
,这个instance
对象就是类NSObject
的实例,对象instance
的isa
指针指向类NSObject
。 - 类里面存储了一些数据,包括分配的大小、成员变量和成员方法等等。
- 调用对象的函数(发送消息)时,
obj_msgSend
从该对象中的类中查找并决定调用哪个,如果找不到则从父类中查找。比如[instance copy]
会从类NSObject
查找方法copy
。有人会问,为什么不从instance
本身去找这个copy
方法?想象一下,如果每生成一个实例都会将所有的方法实现拷贝过去,那将会占用很大的内存,所以类生成实例的时候将实例的isa指向自己,调用函数时在isa指向的类中去执行该调用哪个方法的逻辑。
由此可知,类NSObject
保存了所有实例方法,以供NSObject的实例
调用。
介绍完类与实例的关系后,回到本文的主题,元类是什么,跟类又是什么关系?之前我们说过,每个对象都是某个类的实例对象,那类NSObject
作为一个对象,那也有它对应的一个类,这个类我们通常叫做元类。元类,我们编码过程中通常不会直接用到,但确实是存在的。跟介绍实例和类的关系一样,我们也从以下几点也弄清类(class
)和元类(metaclass
)的关系:
-
class
是metaclass
的一个实例,class
的isa
指向metaclass
,但我们通常不会直接用到metaclass
。 -
metaclass
里面也和class
一样,存储了一些变量和方法数据。 - 向
class
发送消息时([NSObject alloc]
),会从metaclass
中找到方法alloc
调用,由此可知,metaclass
里面存储的是类方法。
接下来通过代码验证一下元类跟类的关系:
void classInfoFunction(id self, SEL _cmd){
NSLog(@"This class is %p\nclass:%@\nsuperClass:%@\n", self, [self class], [self superclass]);
Class currentClass = [self class];
int i = 0;
while(i++ < 4){
NSLog(@"currentClass:%p", currentClass);
currentClass = object_getClass(currentClass);
}
}
NSLog(@"NSObject class:%p metaclass:%p", [NSObject class], object_getClass([NSObject class]));
// 通过runtime注册一个NSObject的子类并添加方法classInfoFunction
Class subClass = objc_allocateClassPair([NSObject class], "testSubClass", 0);
class_addMethod(subClass, @selector(classInfo), (IMP)classInfoFunction, "v@:");
objc_registerClassPair(subClass);
id subClassInstance = [subClass new];
[subClassInstance performSelector:@selector(classInfo)];
// 以下是打印的信息
NSObject class:0x10072e148 metaclass:0x10072e0f8
This class is 0x102b00680
class:testSubClass
superClass:NSObject
currentClass:0x102b00650
currentClass:0x102b00c80
currentClass:0x10072e0f8
currentClass:0x10072e0f8
从上面的代码中我们可以得出上图,跟图一的规则保持一致。
注册类源码分析:
接下来我们看一下objc_allocateClassPair(Class superclass, const char *name, size_t extraBytes)
,我们先简化一下该函数:
Class objc_allocateClassPair(Class superclass, const char *name, size_t extraBytes){
Class cls = alloc_class_for_subclass(superclass, extraBytes);
Class meta = alloc_class_for_subclass(superclass, extraBytes);
objc_initializeClassPair_internal(superclass, name,cls,meta);
return cls;
}
objc_allocateClassPair
分配了两个类cls
和meta
,并只将cls
作为新生成的类返回,这也说明了metaclass
对于我们来说是隐藏的,平时不需要用到。那么class
和metaclass
又是如何关联起来的呢?接下来看一下简化的函数objc_initializeClassPair_internal
:
static void objc_initializeClassPair_internal(Class superclass, const char *name, Class cls, Class meta){
// 初始化cls和meta的大小和数据
cls->setData();
meta->setData();
cls->data()->flags = 0; // 普通类
meta->data()->flags = RO_META; // 标记为meta
cls->initClassIsa(meta); // 将cls的isa指向meta,这就是我们说的第1点
if(superclass){
// 有父类
meta->initClassIsa(superclass->ISA()->ISA()); // meta的isa指向根类的meta
cls->superclass = superclass;
meta->superclass = superclass->ISA(); // meta的父类为父类的meta
}else{
// 作为根类
meta->initClassIsa(meta); // 根类的isa指向自己
cls->superclass = Nil;
meta->superclass = cls; // 根类的父类指向cls
}
}
通过简化后,我们可以清晰地看出,函数objc_initializeClassPair_internal
主要做了这几件事:
- 初始化
cls
和meta
的数据,并将meta
的flags
标记为RO_META
- 根据是否为根类分别初始化
cls
和meta
的isa
和superclass
总结:
元类中保存了类的实例方法的实现,父类为对应类的父类的元类,isa指针指向根类的元类。
参考文档:
1.Classes and metaclasses
2.objc_explain_Classes_and_metaclasses