OC底层原理之-类的结构

1,334 阅读6分钟

前言

之前写的文章对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做准备。