前言:我是参考 南峰子 的博客加上自己理解写的,原著专辑大家自己可看:http://southpeak.github.io/categories/objectivec/
iOS中的类(Class)和对象(objc)都是在RunTime 中实现的,自己学习人家的博客做点笔记,提醒一下自己:
类(Class)
为什么RunTime和这些扯上关系了,好吧,看看人家的目录结构你会发现在OC代码的执行都是离不开RunTime。而代码无非就是类与方法,所以运行时定义了类和方法,并控制他们的执行方式。目录结构如下:
Objective-C 中的类使用的是objc_class 这个结构体表示,在目录objc/runtime.h中查看:
struct objc_class {
Class isa OBJC_ISA_AVAILABILITY;
#if !__OBJC2__
Class super_class OBJC2_UNAVAILABLE;
const char *name OBJC2_UNAVAILABLE;
long version OBJC2_UNAVAILABLE;
long info OBJC2_UNAVAILABLE;
long instance_size OBJC2_UNAVAILABLE;
struct objc_ivar_list *ivars OBJC2_UNAVAILABLE;
struct objc_method_list **methodLists OBJC2_UNAVAILABLE;
struct objc_cache *cache OBJC2_UNAVAILABLE;
struct objc_protocol_list *protocols OBJC2_UNAVAILABLE;
#endif
} OBJC2_UNAVAILABLE;
isa
对象指针,Class结构本身也是一个对象,里面的isa指针指向的就是这个对象对应的类,但是Class对象指向的这个类叫做元类metaClass(下面介绍),在这个类调用类方法就可以看出元类的作用了。
super_class
父类,如果该类已经是最顶层的根类(如NSObject或NSProxy),则super_class为NULL
name
类名
cache
struct objc_cache {
unsigned int mask /* total = mask + 1 */ OBJC2_UNAVAILABLE;
unsigned int occupied OBJC2_UNAVAILABLE;
Method buckets[1] OBJC2_UNAVAILABLE;
};
- mask
一个整数,指定分配的缓存bucket的总数。在方法查找过程中,Objective-C runtime使用这个字段来确定开始线性查找数组的索引位置。指向方法selector的指针与该字段做一个AND位操作(index = (mask & selector))。这可以作为一个简单的hash散列算法 - occupied
一个整数,指定实际占用的缓存bucket的总数 - buckets
指向Method数据结构指针的数组。这个数组可能包含不超过mask+1个元素。需要注意的是,指针可能是NULL,表示这个缓存bucket没有被占用,另外被占用的bucket可能是不连续的。这个数组可能会随着时间而增长
应这个消息的对象。在实际使用中,这个对象只有一部分方法是常用的,很多方法其实很少用或者根本用不上。这种情况下,如果每次消息来时,我们都是methodLists中遍历一遍,性能势必很差。这时,cache就派上用场了。在我们每次调用过一个方法后,这个方法就会被缓存到cache列表中,下次调用的时候runtime就会优先去cache中查找,如果cache没有,才去methodLists中查找方法。这样,对于那些经常用到的方法的调用,但提高了调用的效率。比如:
NSArray *array = [[NSArray alloc] init];
方法执行流程
1.[NSArray alloc]先被执行。因为NSArray没有+alloc方法,于是去父类NSObject去查找。
2.检测NSObject是否响应+alloc方法,发现响应,于是检测NSArray类,并根据其所需的内存空间大小开始分配内存空间,然后把isa指针指向NSArray类。同时,+alloc也被加进cache列表里面。
3.接着,执行-init方法,如果NSArray响应该方法,则直接将其加入cache;如果不响应,则去父类查找。
4,在后期的操作中,如果再以[[NSArray alloc] init]这种方式来创建数组,则会直接从cache中取出相应的方法,直接调用。
对象(objc)
在objc/objc.h 中我们看到如下定义
struct objc_object {
Class isa OBJC_ISA_AVAILABILITY;
};
可以发现对象结构体就一个isa指针(刚才不是说Class也是元类(metaClass)的对象,所以Class 数据结构中也有一个isa指针)。它指向这个对象对应的类Class上。
NSObject类的alloc和allocWithZone:方法使用函数创建对象时,会调用class_createInstance来创建这个对象结构体objc_objc。
当我们这个调用这个对象的方法selector,RunTime 会通过isa指针找到这个对象对应的类,然后在这个类的方法列表(objc_method_list)中查找这个方法selector,如果找不到就往这个类的父类上面找。找到后执行这个方法。
特殊的类--元类(Meta Class)
上面我们说了,Class本身也是各对象,它也有isa指针,那么它的isa指向了什么地方了?答案就是Meta Class。
meta-class是一个类对象的类。
当我们向一个对象发送消息时,runtime会在这个对象所属的这个类的方法列表中查找方法;而向一个类发送消息时,会在这个类的meta-class的方法列表中查找。
比如下面我们初始化NSArray用类方法实现:
NSArray *array = [NSArray array];
这里 +array 方法被NSArray调用,这里NSArray是个类,也是个对象objc_object,这个对象不是
通过alloc或allocWithZone:方法创建的,所以没用
通过NSArray 父类NSObject最后通过Class 的class_createInstance创建objc_object。它本身就是objc_object,也有isa指针,为了找到这个方法,这个指针指向那了,其实就是指向NSArray的元类。
meta-class之所以重要,是因为它存储着一个类的所有类方法
。每个类都会有一个单独的meta-class,因为每个类的类方法基本不可能完全相同。
再深入一下,meta-class也是一个类,也可以向它发送一个消息,那么它的isa又是指向什么呢?为了不让这种结构无限延伸下去,Objective-C的设计者让所有的meta-class的isa指向基类的meta-class,以此作为它们的所属类。即,任何NSObject继承体系下的meta-class都使用NSObject的meta-class作为自己的所属类,而基类的meta-class的isa指针是指向它自己。这样就形成了一个完美的闭环。
结构图:
[图片上传失败...(image-70292-1513910965638)]
其中 Instance of SubClass是某个类的实例对象,它的isa 指针指向SubClass类,如果把Subclass类看成一个对象,那么它的isa指向指向Subclass的元类(meta)。其中元类和元类之间继承关系,不一定依赖于类于类之间继承,但是最后指向了RootClass 根类和RootClass meta根类的元类。其中根类的元类(RootClass meta)又指向根类自己Root Class。
下面有个小例子,帮组自己理解一下
- (void)viewDidLoad {
[super viewDidLoad];
//实例方法-(Class)class,是获取这个实例对象对应的类
//类方法+(Class)class 是获取这个类(本身是个实例对象)对应的类
NSLog(@"这个类名是:%@ 父类名是:%@ 父父类名:%@ 父父父类:%@",[self class],[self superclass],[[self superclass] superclass],[[[self superclass] superclass] superclass]);
NSLog(@"NSObject这个类名:%@ 的地址:%p",[NSObject class],[NSObject class]);
NSLog(@"NSOject meta 地址:%p",objc_getClass((__bridge void *)[NSObject class]));
NSLog(@"这个类的地址:%p",[ViewController class]);
NSLog(@"这个对象地址:%p",self);
Class prClass = [self class];
/**
* for循环打印对象的isa地址
* 第一次打印的是这个对象对应的类(ViewController)的地址,也就是对象isa地址会发现其指向。
* 第二次打印的是ViewController看成对象后对应类的地址,也就是ViewController 的isa指向地址,也就是ViewController的元类。
* 第三次,第四次是元类的元类.....
*
*/
for (int i = 0; i< 5; i++) {
NSLog(@"循环第%i次打印对象isa地址:%p",i+1,prClass);
//objc_getClass得到一个类,根据类名(字符串)得到。如果类再
prClass = objc_getClass((__bridge void *)prClass);
}
}
输出结果:
**2016-07-27 11:45:40.088 RunTime_****类与对象****[73165:31955041] ****这个类名是:****ViewController ****父类名是:****UIViewController ****父父类名:****UIResponder ****父父父类****:NSObject**
**2016-07-27 11:45:40.088 RunTime_****类与对象****[73165:31955041] NSObject****这个类名:****NSObject ****的地址:****0x109145170**
**2016-07-27 11:45:40.089 RunTime_****类与对象****[73165:31955041] NSOject meta ****地址:****0x0**
**2016-07-27 11:45:40.089 RunTime_****类与对象****[73165:31955041] ****这个类的地址:****0x1088e7d58**
**2016-07-27 11:46:54.516 RunTime_****类与对象****[73165:31955041] ****这个对象地址:****0x7facd249fb40**
**2016-07-27 11:47:21.049 RunTime_****类与对象****[73165:31955041] ****循环第****1****次打印对象****isa****地址****:0x1088e7d58**
**2016-07-27 11:47:54.710 RunTime_****类与对象****[73165:31955041] ****循环第****2****次打印对象****isa****地址****:0x0**
**2016-07-27 11:47:54.711 RunTime_****类与对象****[73165:31955041] ****循环第****3****次打印对象****isa****地址****:0x0**
**2016-07-27 11:47:54.711 RunTime_****类与对象****[73165:31955041] ****循环第****4****次打印对象****isa****地址****:0x0**
**2016-07-27 11:47:54.711 RunTime_****类与对象****[73165:31955041] ****循环第****5****次打印对象****isa****地址****:0x0**
代码注释中我说明了这种关系。我画了一个简易的图如下:
好吧,第一课学习到这吧,我要改代码去了! 屌丝........