(一)类的本质
- 类对象(class object)与实例对象(instance object)
类本身也是一个对象,是class类型的对象,简称“类对象”。
在/usr/include/objc/objc.h 和 runtime.h 中找到对 class 与 object 的定义:
Class 是一个 objc_class 结构类型的指针;而 id(任意对象)是一个 objc_object 结构类型的指针, 其第一个成员是一个 objc_class 结构类型的指针。注意这里有一关键的引申解读:内存布局以一个 objc_class 指针为开始的所有东东都可以当做一个 object 来对待! 那 objc_class 又是怎样一个结 构体呢?且看:
typedef struct objc_class *Class; typedef struct objc_object { Class isa; } *id;
objc_class 又是怎样一个结 构体呢?
1 struct objc_class { 3 struct objc_class* isa; 4 struct objc_class* super_class; 5 const char* name; 6 long version; 7 long info;//供运行期使用的一些位标识。有如下一些位掩码:CLS_CLASS (0x1L) 表示该类为普通 class ,其中包含实例方法和变量; 8 //CLS_META (0x2L) 表示该类为 metaclass,其中包含类方法; 10 //CLS_METHOD_ARRAY (0x100L) 该标志位指示 methodlists 是指向一个 objc_method_list 还是 一个包含 objc_method_list 指针的数组; 11 12 long instance_size;//该类的实例变量大小(包括从父类继承下来的实例变量) 14 struct objc_ivar_list* ivars;//指向 objc_ivar_list 的指针,存储每个实例变量的内存地址,如果该类没有任何实例变量则为NULL 16 struct objc_method_list** methodLists; //与 info 的一些标志位有关,CLS_METHOD_ARRAY 标识位决定其指向的东西(是指 向单个 objc_method_list 还是一个 //objc_method_list 指针数组),如果 info 设置了 CLS_CLASS 则 objc_method_list 存储实例方法, 18 //如果设置的是 CLS_META 则存储类方法 20 struct objc_cache* cache; //指向 objc_cache 的指针,用来缓存最近使用的方法,以??高效率; 22 struct objc_protocol_list* protocols;//指向 objc_protocol_list 的指针,存储该类声明要遵守的正式协议 24 };
类对象中的 isa 指向类结构 被称作 metaclass,metaclass 存储类的 static 类成员变量与 static 类成员方法(+开头的方法);实 例对象中的 isa 指向类结构称作 class(普通的),class 结构存储类的普通成员变量与普通成员方法(- 开头的方法)。
- 在继承层次中,子类,父类,根类(这些都是普通 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"];
- 总结:
ObjC 为每个类的定义生成两个 objc_class ,一个即普通的 class,另一个即 metaclass。我们可以在 运行期创建这两个 objc_class 数据结构,然后使用 objc_addClass 动态地创建新的类定义
(二)类的加载和初始化
1 #import "Person.h" 2 3 @implementation Person 4 5 6 +(void)load 7 { 8 NSLog(@"%s",__func__); 9 } 10 11 +(void)initialize 12 { 13 NSLog(@"%s",__func__); 14 } 15 @end
1 #import "Student.h" 2 3 @implementation Student 4 +(void)load 5 { 6 NSLog(@"%s",__func__); 7 } 8 9 +(void)initialize 10 { 11 NSLog(@"%s",__func__); 12 } 13 @end
测试程序:
1 int main(int argc, const char * argv[]) { 2 @autoreleasepool { 3 // insert code here... 4 NSLog(@"Hello, World!"); 5 6 Person* p1=[Person new]; 7 8 // Person* p2=[Person new];//注释掉结果不变 9 10 Student* s1=[Student new]; 11 12 } 13 return 0; 14 }
运行结果:
2015-10-19 14:15:11.614 TClass[2447:116989] +[Person load]
2015-10-19 14:15:11.615 TClass[2447:116989] +[Student load]
2015-10-19 14:15:11.616 TClass[2447:116989] Hello, World!
2015-10-19 14:15:11.616 TClass[2447:116989] +[Person initialize]
2015-10-19 14:15:11.616 TClass[2447:116989] +[Student initialize]
总结:
1.当程序启动时,就会加载项目中所有的类和分类,而且加载后会调用每个类和分类的+load方法,只会调用一次;
2.当第一次使用某个类时,就会调用当前类的+initialize方法;
3.先加载父类,再加载子类(先调用父类的+load方法,再调用子类的+load方法,最后调用分类的+load方法),先初始化父类,再初始化子类(先调用父类的+initialize方法,再调用子类的+initialize方法)。
4.注意:在初始化的时候,如果在分类中重写了+initialize方法,则会覆盖掉父类的。
5.重写+initialize方法可以监听类的使用情况。
(三)分类
分类的作用:在不改变原来的类内容的基础上,为类增加一些方法。
在分类中添加一个方法
Study方法的实现
(二)分类的使用注意
(1)分类只能增加方法(包括类方法和对象方法),不能增加成员变量
(2)在分类方法的实现中可以访问原来类中的成员变量;
(3)分类中可以重新实现原来类中的方法,但是会覆盖掉原来的方法,导致原来的方法无法再使用(警告);
(4)方法调用的优先级:分类->原来的类->父类,若包含有多个分类,则最后参与编译的分类优先;
(5)在很多的情况下,往往是给系统自带的类添加分类,如NSObject和NSString,因为有的时候,系统类可能并不能满足我们的要求。
(6)在大规模的应用中,通常把相应的功能写成一个分类,可以有无限个分类,对原有类进行扩充,一般分模块写,一个模块一个分类。