前言
之前写的文章对OC的对象有了比较深的理解。OC对象理解传送门。其中我在最后一片文章OC底层原理之-OC对象(下)isa指针结构分析的结尾处通过指针打印验证了类对象isa指针指向类,类的isa指针指向元类,元类的isa指针指向根元类,根元类ias指针指向自己。下面在温习一下
isa指针的指向
我们创建两个类:Person继承自NSObject,Man继承自Person
@interface Person : NSObject
@end
@interface Man : Person
@end
运行打印:
上图是Preson的isa指向,我们再看下Man的isa指向
上图是Man的isa指针,通过上面两个图,我们发现isa指针的指向跟父类无任何关系
下面我们再看下继承关系
可以看到Man的父类是Person,Person的父类是NSObject.继承跟isa指针的关系可以用下图展示出来
通过图我们记住几个关键点。(对象方法和属性存放在类中,元类就是存放类方法的)
1.对象没有继承关系,继承关系只存在类对象以及元类对象上2.isa指针不会指向父类3.根元类(NSObject)的isa指针指向自己,但是继承自NSObject类。
上面是通过打印地址信息知道的
类的唯一性
我们通过上面的打印可以看出来,打印的NSObject的地址都是0x000000001003340f0,无论是person的还是man的根元类NSObject地址都一样,这也说明根元类NSObject在内存中的存在是唯一的。
下面我们看下类是否是唯一的 写如下代码
Class class1 = [Person class];
Class class2 = [Person alloc].class;
Class class3 = object_getClass([Person alloc]);
NSLog(@"%p->%p->%p", class1, class2, class3);
我们查看打印结果:
我们发现打印的地址都一样,这说明
类对象在内存中的存在也是唯一的,只有一份。
上面是通过打印地址发现的唯一性现象,下面我们继续探究类的结构,我们来通过源码分析
类的结构分析
这里我们使用的源码为:objc4-781
类的结构
上面我们获取父类用的方法是man.superclass
点进superclass是
再点击Class我们就会看到下面的方法
记住这两个方法
我们点击objc_class发现下面代码
通过上图我们可以看到objc_class继承自objc_object,那么objc_object内部又有什么?
方法太长截取了最开始一部分,我们发现里面包好
isa。这也就是所有对象的创建,都会有isa,而且同时还会有父类:superclass,cache,bits等信息。那这些信息里面都有什么呢?
类结构内部分析
探究class_data_bits_t bits
cache_t是缓存,里面包含缓存内容,点击进去
上图发现比较难懂,不知道都是什么
我们再看看要研究的class_data_bits_t
发现里面的东西也难懂,那用什么办法能够探究其内部结构呢?
内存偏移
下面介绍内存偏移 在main函数中写如下代码:
int main(int argc, const char * argv[]) {
@autoreleasepool {
int a = 10;
int b = 10;
NSLog(@"%d---%p", a, &a);
NSLog(@"%d===%p", b, &b);
Person *person = [Person alloc];
Person *person1 = [Person alloc];
NSLog(@"%@--->%p", person, &person);
NSLog(@"%@===>%p", person1, &person1);
}
return 0;
}
运行打印结果如下:
之所打印如下结果因为:
通过上图我们可以知道,a,b是对10进行了
值拷贝,a,b是变量person输出的事Person对象的地址,&person输出的是保存这个地址的内存地址
我们继续写下面代码
int c[4] = {1,2,3,4};
int *d = c;
NSLog(@"%p->%p->%p", &c, &c[0], &c[1]);
NSLog(@"%p=>%p=>%p", d, d+1, d+2);
打印如下:
我们发现
c起始位置和c的第一个元素位置是一样的,而d的位置和c的第一个元素位置一样,d+1和c的第二个元素位置一样。通过上面可以看出我们可以直接通过位置偏移来去值。
下面验证下,写如下代码
for (int i = 0; i < 4; i++) {
int value = *(d+i);
NSLog(@"-->%d", value);
}
打印结果
说明通过内存偏移确实可以取值。*(d+i)意思是取出d+i地址下的值
探究class_data_bits_t bits的内存偏移
上面我们知道只要知道这个属性在这个结构体的内存偏移位置,我们就可以得到该结构体的内存地址,进而打印出其内部信息
我们要向知道class_data_bits_t bits的偏移位置,必须知道它前面:Class ISA(从objc_object继承而来),Class superclass,cache_t cache所占位置大小。
Class ISA以及Class superclass都是指针,所以都是8字节
下面看看cache_t cache的内存大小(cache_t它的大小由它内部属性决定的)
上图是cache_t方法的内部实现
我们发现属性很多,但是记住
static不占该类内存空间,它属于全局变量,这样就只需要找不带static属性。
综上所述,cache_t占用的是16字节。加上之前的isa的8字节,superclass的8字节,所以
class_data_bits_t bits在结构体的内存偏移是32字节。
通过内存偏移探究class_data_bits_t bits内部结构
我们在Person.h写如下代码:
@interface Person : NSObject
@property (nonatomic, copy)NSString *name;
@property (nonatomic, copy)NSString *address;
@property (nonatomic, assign) NSInteger age;
- (void)likeFood;
- (NSString *)sleepTime;
+ (void)likeColor;
+ (NSString *)workTime;
@end
创建Person对象
打断点,进行打印
我们打印了属性
里面调用方法解释,后面获取对象方法也是一样的
![]()
我们刚才拿到了属性,下面我们再去拿方法
上面的方法我们发现[Person .cxx_destruct]这个方法,这个方法原本是为了C++对象析构的,ARC借用了这个方法插入代码实现了自动内存释放的工作。
我们发现不存在类方法,原因是我们上面探究的是类的class_data_bits_t,类的class_data_bits_t存放的是对象的属性以及对象方法,类方法存放在元类中。我们要找到元类的class_data_bits_t才能找到类方法
探究类方法
上面Person.class是类对象,根据类对象的ias指针指向元类这一特性,我们去找Person元类。
过程和对象方法是一致的,这里就不说什么了
最后
这篇文章介绍了isa指针,继承的关系(对象没有继承关系,NSObject元类继承NSObject),介绍了类里包含isa指针,superclass,cache_t,class_data_bits_t,我们由主要介绍了class_data_bits_t,其内部包含class_rw_t。class_rw_t内部又存在methods(),properties(),protocols()这三个方法,他们分别存对象方法,属性,代理协议。 这部分东西很绕,对象的isa指向类,类的指向元类,元类指向根元类。打印地址,isa指针地址,对象首地址,分清楚多敲多理解。这是打开类的内部,看到里面都有什么,为后面的runTime做准备。