OC中的Class

先看一个示例

MyClass *my = [MyClass new];
BOOL b1 = [my responseToSelector:@selector(responseToSelector:)];
BOOL b2 = [MyClass responseToSelector:@selector(responseToSelector:)];
NSLog(@"%d, %d", b1, b2);

结果:都输出”1”(YES)。

Class和实例对象

所有的对象都是由其对应的类实例化而来,殊不知类本身也是一种对象。在Objective-C中,我们用到的几乎所有类都是NSObject类的子类,NSObject类定义格式如下:

@interface NSObject <NSObject> {
Class isa;
}

在objc.h中我们发现这个Class仅仅是一个结构(struct)指针的typedef定义:

typedef struct objc_class *Class;

在Objective-C2.0中,objc_class的定义如下:

struct objc_class {
Class isa;
}

在Objective-C中任何的类定义都是对象。即在程序启动的时候任何类定义都对应于一块内存。在编译的时候,编译器会给每一个类生成一个且只生成一个”描述其定义的对象”,也就是苹果公司说的类对象(class object),是一个单例。C++等语言中所谓的对象,叫做实例对象(instance object)。Objective-C是门很动态的语言,因此程序里的所有实例对象(instace object)都是在运行时由Objective-C的运行时库生成的,而这个类对象(class object)就是运行时库用来创建实例对象(instance object)的依据。实例对象(instance object)的isa指针指向的类对象(class object)里面还有一个isa。这个类对象(class objec)的isa指向的依然是一个objc-class,它就是“元类对象”(metaclass object)。

尽管类对象保留了一个类实例的原型,但它并不是实例本身。它没有自己的实例变量,也不能执行那些类的实例的方法(只有实例对象才可以执行实例方法)。然而,类的定义能包含那些特意为类对象准备的方法–类方法( 而不是的实例方法)。类对象从父类那里继承类方法,就像实例从父类那里继承实例方法一样。

类对象(class object)

类对象像其他对象一样,也是id类型。类对象是由编译器创建的,即在编译时所谓的类,就是指类对象。任何直接或间接继承了NSObject的类,它的实例对象(instance objec)中都有一个isa指针,指向它的类对象(class object)。这个类对象(class object)中存储了关于这个实例对象(instace object)所属的类的定义的一切:包括变量,方法,遵守的协议等等。因此,类对象能访问所有关于这个类的信息,利用这些信息可以产生一个新的实例,但是类对象不能访问任何实例对象的内容。当你调用一个 “类方法” 例如 [NSObject alloc],你事实上是发送了一个消息给他的类对象。类对象是一个功能完整的对象,所以也能被动态识别(dynamically typed),接收消息,从其他类继承方法。特殊之处在于它们是由编译器创建的,缺少它们自己的数据结构(实例变量),只是在运行时产生实例的代理。

元类对象(metaclass object)

类对象是元类对象的一个实例!元类描述了 一个类对象,就像类对象描述了普通对象一样。不同的是元类的方法列表是类方法的集合,由类对象的选择器来响应。当向一个类发送消息时,objc_msgSend会通过类对象的isa指针定位到元类,并检查元类的方法列表(包括父类)来决定调用哪个方法。元类代替了类对象描述了类方法,就像类对象代替了实例对象描述了实例化方法。元类也是对象,元类是根元类(root class’s metaclass)的实例,而根元类是其自身的实例,即根元类的isa指针指向自身。

类对象(class object)中包含了类的实例变量,实例方法的定义,而元类对象(metaclass object)中包括了类的类方法(也就是C++中的静态方法)的定义。类对象存的是关于实例对象的信息(变量,实例方法等),而元类对象(metaclass object)中存储的是关于类的信息(类的版本,名字,类方法等)。类对象(class object)和元类对象(metaclass object)的定义都是objc_class结构,其不同仅仅是在用途上,比如其中的方法列表在类对象(instance object)中保存的是实例方法(instance method),而在元类对象(metaclass object)中则保存的是类方法(class method)。

当一个消息发送给任何一个对象, 方法的检查 从对象的 isa 指针开始,然后是父类。实例方法在类中定义, 类方法 在元类和根类中定义。(根类的元类就是根类自己)。在一些计算机语言的原理中,一个类和元类层次结构可以更自由的组成,更深元类链和从单一的元类继承的更多的实例化的类。Objective-C 的类方法 是使用元类的根本原因,在其他方面试图在隐藏元类。例如 [NSObject class] 完全相等于 [NSObject self],所以,在形式上他还是返回的 NSObject->isa 指向的元类。 Objective-C语言是一组实用的折中方案。

总结

在objc中不论是实例对象还是Class,都是id类型的对象(Class同样是对象);实例对象的isa指向它的Class(储存所有减号方法),Class对象的isa指向元类(储存所有加号方法);向一个对象(id类型)发送消息时,都是从这个对象的isa指针指向的Class中寻找方法;类的super_class指向其父类,而元类的super_class则指向父类的元类。元类的super class链与类的super class链平行,所以类方法的继承与实例方法的继承也是并行的。而根元类(root class’s metaclass)的super_class指向根类(root class)。object_getClass跟随实例的isa指针,返回此实例所属的类,对于实例对象(instance)返回的是类(class),对于类(class)则返回的是元类(metaclass);-class方法对于实例对象(instance)会返回类(class),但对于类(class)则不会返回元类(metaclass),而只会返回类本身,即[@”instance” class]返回的是__NSCFConstantString,而[NSString class]返回的是NSString。class_isMetaClass可判断某类是否为元类。

isa

回到题目,当像MyClass类发送一个实例方法(- responseToSelector)消息时:会从它的isa,也就是MyClass元类对象中寻找,由于元类中的方法都是类方法,所以自然找不到;于是沿继承链去父类NSObject元类中寻找,依然没有;由于objc对这块的设计是,NSObject的元类的父类是NSObject类(也就是我们熟悉的NSObject类),其中有所有的实例方法,因此找到了- responseToSelector。NSObject类中的所有实例方法很可能都对应实现了一个类方法(至少从开源的代码中可以看出来),如+ resonseToSelector,但并非公开的API,如果真的是这样,第2步就可以找到这个方法。 非NSObject的selector这样做无效。

使用objc_allocateClassPair可在运行时创建新的类与元类对,使用class_addMethod和class_addIvar可向类中增加方法和实例变量,最后使用objc_registerClassPair注册后,就可以使用此类了。动态语言可以在需要时更改已经定义好的类!由于在编译的时候就为每个类分配了内存空间大小,根据他们的属性,而且这些属性直接保存在名objc_ivar_list的struct容器里,一旦编译后就锁定了大小,所以不能在运行时随意增加,也没有空间再分给他们。之所以category可以是因为他的方法是存在名为objc_method_list的指针指向的struct容器里,由此可以看出属性是全部都是丢在类里,而方法只是把装他们的容器的地址丢在类里,所以属性不能扩展,而方法可以无限制,因为他的大小不影响类的空间。