前两天,公司一个新来的实习生问我一个问题,什么是类,这个问题看似很好笑,但是要答全也确实不那么简单!!!
回去翻看资料,查看以往的笔记,那么就说说我自己对类的本质看法!
在OOP的设计的模式下,我们说万物皆对象,OC是一种面向对象的弱语法,但其实也是所有用到的东西也都是对象,包括成员变量!那么问题来了,对象从哪里来,这就要说到类!
类的本质:类的本质其实也是一个对象,叫类对象。
那么我们就来看看类和对象到底有什么联系和区别:
首先,我找到了官方文档中对class与boject的定义:
typedef struct objc_class *Class;
typedef struct objc_object {
Class isa;
} *id;
Class 是一个
objc_class 结构类型的指针;而
id(任意对象) 是一个
objc_object 结构类型的指针,其第一个成员是一个
objc_class 结构类型的指针。注意这里有一关键的引申解读:内存布局以一个 objc_class 指针为开始的所有东东都可以当做一个
object 来对待! 那
objc_class 又是怎样一个结构体呢?且看:
struct objc_class
{
struct objc_class* isa;
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;
};
objc_class 结构体的各成员介绍如下:
isa:是一个
objc_class 类型的指针,看到这里,想起我前面的引申解读了没?内存布局以一个
objc_class 指针为开始的所有东东都可以当做一个
object 来对待! 这就是说
objc_class 或者说类其实也可以当做一个
objc_object 对象来对待!对象是对象,类也是对象,是不是有点混淆?别急,
ObjC发明(or 重用)了一个术语来区分这两种不同的对象:类对象(
class object)与实例对象(
instance object)。OK,名称混淆的问题解决,下面我将使用这两个术语来区分不同的对象,而使用“对象”这一术语来泛指所有的对象。
ObjC还对类对象与实例对象中的
isa 所指向的类结构作了不同的命名:类对象中的
isa 指向类结构被称作
metaclass,
metaclass 存储类的
static类成员变量与
static类成员方法(+开头的方法);实例对象中的
isa 指向类结构称作
class(普通的),
class 结构存储类的普通成员变量与普通成员方法(-开头的方法)。
super_class:一看就明白,指向该类的父类呗!如果该类已经是最顶层的根类(如
NSObject 或
NSProxy),那么
super_class 就为
NULL。
好,先中断一下其他类结构成员的介绍,让我们厘清一下在继承层次中,子类,父类,根类(这些都是普通
class)以及其对应的
metaclass 的
isa 与
super_class 之间关系:
规则一:类的实例对象的
isa 指向该类;该类的
isa 指向该类的
metaclass;
规则二:类的
super_class 指向其父类,如果该类为根类则值为
NULL;
规则三:
metaclass 的
isa 指向根
metaclass,如果该
metaclass 是根
metaclass 则指向自身;
规则四:
metaclass 的
super_class 指向父
metaclass,如果该
metaclass 是根
metaclass 则指向该
metaclass 对应的类;
好吧,文字总是那么乏力,有图有真相!
那么
class 与
metaclass 有什么区别呢?
class 是
instance object 的类类型。当我们向实例对象发送消息(实例方法)时,我们在该实例对象的
class 结构的
methodlists 中去查找响应的函数,如果没找到匹配的响应函数则在该 class 的父类中的
methodlists 去查找(查找链为上图的中间那一排)。如下面的代码中,向str 实例对象发送
lowercaseString 消息,会在
NSString 类结构的
methodlists 中去查找
lowercaseString 的响应函数。
NSString * str;
[str lowercaseString];
metaclass 是
class object 的类类型。当我们向类对象发送消息(类方法)时,我们在该类对象的
metaclass 结构的
methodlists 中去查找响应的函数,如果没有找到匹配的响应函数则在该 metaclass 的父类中的
methodlists 去查找(查找链为上图的最右边那一排)。如下面的代码中,向
NSString 类对象发送
stringWithString 消息,会在
NSString 的
metaclass 类结构的
methodlists 中去查找
stringWithString 的响应函数。
[NSString stringWithString:@"str"];
好,至此我们明白了类的结构层次,让我们接着看类结构中的其他成员。
name:一个 C 字符串,指示类的名称。我们可以在运行期,通过这个名称查找到该类(通过:
id objc_getClass(const char *aClassName))或该类的
metaclass(
id objc_getMetaClass(const char *aClassName));
version:类的版本信息,默认初始化为 0。我们可以在运行期对其进行修改(
class_setVersion)或获取(
class_getVersion)。
info:供运行期使用的一些位标识。有如下一些位掩码:
CLS_CLASS (0x1L) 表示该类为普通
class ,其中包含实例方法和变量;
CLS_META (0x2L) 表示该类为
metaclass,其中包含类方法;
CLS_INITIALIZED (0x4L) 表示该类已经被运行期初始化了,这个标识位只被
objc_addClass 所设置;
CLS_POSING (0x8L) 表示该类被
pose 成其他的类;(
poseclass 在ObjC 2.0中被废弃了);
CLS_MAPPED (0x10L) 为
ObjC运行期所使用
CLS_FLUSH_CACHE (0x20L) 为
ObjC运行期所使用
CLS_GROW_CACHE (0x40L) 为
ObjC运行期所使用
CLS_NEED_BIND (0x80L) 为
ObjC运行期所使用
CLS_METHOD_ARRAY (0x100L) 该标志位指示
methodlists 是指向一个
objc_method_list 还是一个包含
objc_method_list 指针的数组;
instance_size:该类的实例变量大小(包括从父类继承下来的实例变量);
ivars:指向
objc_ivar_list 的指针,存储每个实例变量的内存地址,如果该类没有任何实例变量则为
NULL;
methodLists:与
info 的一些标志位有关,
CLS_METHOD_ARRAY 标识位决定其指向的东西(是指向单个
objc_method_list还是一个
objc_method_list 指针数组),如果
info 设置了
CLS_CLASS 则
objc_method_list 存储实例方法,如果设置的是
CLS_META 则存储类方法;
cache:指向
objc_cache 的指针,用来缓存最近使用的方法,以提高效率;
protocols:指向
objc_protocol_list 的指针,存储该类声明要遵守的正式协议。
总结:
ObjC 为每个类的定义生成两个
objc_class ,一个即普通的
class,另一个即
metaclass。我们可以在运行期创建这两个
objc_class 数据结构,然后使用
objc_addClass 动态地创建新的类定义。