iOS 对象和类的本质

前言

我们编写的OC代码,其实底层实现都是C/C++代码。所以,对象和类也都是基于C/C++的数据结构实现的。 所以你能猜到OC的对象和类是通过什么数据结构实现的吗?

1、instance-实例对象

1.1、定义

实例对象是通过类alloc出来的对象,每次调用alloc都会产生新的实例对象。eg:

NSObject *object1 = [[NSObject alloc] init];
NSObject *object2 = [[NSObject alloc] init];

以上object1、object2都是实例对象,占用两块儿不同的内存。

1.2、底层实现

首先,终端定位到需要转化文件的文件夹下,用以下命令行将OC代码转成C++/C代码。

  xcrun  -sdk  iphoneos  clang  -arch  arm64  -rewrite-objc  OC源文件(eg:main.m)  -o  输出的CPP文件(eg:main.cpp)

NSObject对象为例,我们用以上方法看看它的底层实现,eg:

int main(int argc, const char * argv[]) {
@autoreleasepool {
    NSObject *obj = [[NSObject alloc] init];
}
return 0;
}

转成C++代码后,我们找到了这个对象的实现

struct NSObject_IMPL {
Class isa;
};
//Class是指针类型,指向objc_class类型的结构体
typedef struct objc_class *Class;

obj对象内部实际上只有一个isa指针,指向objc_class类型的结构体。 那isa指针到底指向谁,它又有什么用呢? 在下文中我们会讲到。

1.3、更复杂的继承结构

我们举一反三,设计一个父类Father,继承于NSObject,再设计一个子类son继承于父类,看看他们的底层实现。eg:

@interface Father : NSObject {
    int _age;
}
@end

@interface Son : Father {
   double _height;
}
@end

把代码转成C++,看看内部实现。直接查找类名_IMPL

struct Father_IMPL {
    struct NSObject_IMPL NSObject_IVARS;
    int _age;
};

struct Son_IMPL {
    struct Father_IMPL Father_IVARS; 
    double _height;
};

上述代码相当于

struct Father_IMPL {
    Class isa;
    int _age;
};

struct Son_IMPL {
    Class isa;
    int _age;
    double _height;
};

所以实例对象的本质是结构体,(在c++文件中查找类名_IMPL就能找到这个结构体),里面有一个isa指针和其他成员变量。所以实例对象在内存中存储的内容是 isa指针 + 其他成员变量

1.4、内存大小

从1.2和1.3我们知道,一个实例对象,在内存中存储的是 isa指针 + 其他成员变量。那系统到底给这个对象分配了多少内存,它实际只需要多少?这两个概念也比较容易混淆,我们来理一理。

1.4.1、实际需要

即创建一个实例对象,实际需要多少内存。
可以通过导入头文件#import <objc/runtime.h>,用class_getInstanceSize方法获得。eg:

 NSLog(@"%zd", class_getInstanceSize([NSObject class]));   //8
 NSLog(@"%zd", class_getInstanceSize([Father class]));     //16
 NSLog(@"%zd", class_getInstanceSize([Son class]));        //24

他们需要的内存分别是81624。我们可以一一推算一下他们是怎么来的。
NSObject:内存中只有一个isa指针,指针在64位系统中占8个字节,所以NSObject类型的对象实际占用8个字节。
Father:isa指针 (8字节)+ int类型变量(4字节)= 12字节
Son:isa指针(8字节)+ int类型变量(4字节)+ double类型变量(8字节) = 20字节。
为什么我们算出来的字节会有偏差?我们可以看看class_getInstanceSize方法的源码。

size_t class_getInstanceSize(Class cls)
{
     if (!cls) return 0;
     return cls->alignedInstanceSize();
}

uint32_t alignedInstanceSize() {
    //返回内存对齐后的大小
    return word_align(unalignedInstanceSize());
}

我们查看class_getInstanceSize源码,align是校准对齐的意思,发现实际的占用大小应该再经过一次内存对齐操作word_align,内存对齐:最后的占用大小是最大元素的倍数。
所以,实际需要内存 = 内存对齐(isa指针 + Ivars)

但是,一个对象实际需要多少内存,系统就会给它实际分配多少内存吗?带着这样的疑问,我们接着往下验证。

1.4.2、实际分配

即创建一个实例对象,实际上分配了多少内存。
可以通过导入头文件#import <malloc/malloc.h>,用malloc_size((__bridge const void *)obj);方法获得。eg:

 NSObject *obj = [[NSObject alloc] init];
 Father *father = [[Father alloc] init];
 Son *son= [[Son alloc] init];

 NSLog(@"%zd", malloc_size((__bridge const void *)obj));     //16
 NSLog(@"%zd", malloc_size((__bridge const void *)father));  //16
 NSLog(@"%zd", malloc_size((__bridge const void *)son));     //32

以上三个对象实际分配的内存分别为161632
我们可以看出,给对象实际分配的内存和对象实际需要的内存是不一样的。
实际上,苹果提前有一块儿一块儿的内存块儿,这些内存块儿都是16的倍数,最大是256。

#define NANO_MAX_SIZE           256 /* Buckets sized {16, 32, 48, 64, 80, 96, 112, ...} */

所以给对象分配内存也是一块儿一块儿分的。当实际需要的内存不是16的倍数,比如24,苹果会分配一块儿最适合的32给它。

思考题🤔:孙类Grandson继承son,Grandson中有两个成员变量int _no; NSString *_name;,那创建一个孙类的实例对象,实际需要多少内存? 系统实际分配了多少内存?

2、class-类对象

从第一部分我们了解到,实例对象只存储isa指针和成员变量,那类里面的方法啊,属性啊等等一些类信息存放在哪里?为什么这些信息没有存放在实例对象里?

因为实例对象可能有很多个,不可能每创建一个实例对象都存一份方法、属性.....的。 这些只需要存一份就可以了。一个类有且只有一个类对象。把属性啊方法啊等信息存在类对象中也再合适不过了。

2.1 创建类对象
NSObject *obj = [[NSObject alloc] init];
Class objClass1 = [NSObject class];
Class objClass2 = [obj class];
Class objClass3 = object_getClass(obj);

我们可以打印一下,objClass1,objClass12,objClass13他们的内存地址都是一样的。也验证了一个类有且一有一个类对象。

2.2 类对象的本质

点进class发现,类对象其实是 objc_class类型的结构体。 我们打开源码,看看这个结构体到底是什么。

objc_class.png

如上我们可以看出,类对象中存放了
1、isa指针
2、super指针
3、类的属性信息(@property)、类的对象方法信息(instance method)
4、类的协议信息(protocol)、类的成员变量信息(ivar)
........

类对象里面也有一个isa指针,还有一个super指针,那他们分别指向哪里,又有什么作用呢? 我们稍后就会讲到。 当然这里还有一个疑问,既然类对象里面存放的是对象方法信息,那类方法信息存放在哪里呢?

3、meta-class-元类对象

构建
 Class objectMetaClass = object_getClass([NSObject class]);

如上,objectMetaClass 就是 NSObject的元类对象,并且 每个类只有一个元类对象
元类对象和类对象的结构是一样的,都是objc_class类型的结构体,元类对象存放类方法信息
1、isa指针
2、super指针
3、类的类方法信息(class method)
.........
meta-class对象和class对象的内存结构是一样的,所以meta-class中也有类的属性信息,类的对象方法信息等成员变量,但是其中的值可能是空的。

4、isa指针和super指针

实例对象,类对象,元类对象中都有isa指针,类对象和元类对象中有super指针。他们分别指向哪里?

4.1、 实例对象的isa指针指向

eg1:子类Son中有一个实例方法- (void)sonTest ,创建一个实例对象son,然后用这个对象调用方法[son sonTest];

类对象中存储着实例方法信息。当实例对象调用实例方法时
实例方法存储在类对象中。实例对象调用实例方法时,实例对象对象通过isa指针找到类对象,进而找到类对象中相应的实例方法进行调用。
实例对象的isa指针指向它的类对象。

4.2、类对象的isa指针指向

元类对象中存储着类方法信息。当类对象调用类方法时,类对象通过isa指针找到元类对象,进而找到元类对象中相应的类方法进行调用,类对象的isa指针指向它的元类对象。

4.3、元类对象的isa指针指向

元类对象的isa指针指向基类的元类对象(eg:Son的元类对象和Father的元类对象都指向NSObject的元类对象)

4.4、类对象的super指针指向

eg4:父类Father中有一个实例方法- (void)fatherTest ,创建一个子类实例对象son,然后用这个对象调用方法[son fatherTest];

从4.1我们知道,son对象的isa指针会找到它的类对象,但是类对象中没有fatherTest这个对象方法,所以类对象会通过它的super指针找到父类的类对象,而fatherTest这个方法是存放在Father的类对象中的,进而调用。类对象的super指针是指向父类的类对象的。
特例:当这个类没有父类时(基类),则指向nil

4.5、元类对象的super指针指向

元类对象的super指针指向父类的元类对象
特例:基类的元类对象super指针指向基类的类对象。

以下是对这些情况的总结图


image.png

结束语:这篇文章是对 小码哥底层原理视频的总结,以及我的理解~ 希望能对各位有所帮助,喜欢就点个赞吧(*  ̄3)(ε ̄ *)

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,185评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,445评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,684评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,564评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,681评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,874评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,025评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,761评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,217评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,545评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,694评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,351评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,988评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,778评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,007评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,427评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,580评论 2 349

推荐阅读更多精彩内容