Objective-C的本质之NSObject对象占用多少内存?
我们通常认为:在C语言中 int占用4个字节,char占用1个字节......其实C标准并没有具体给出规定哪个基本类型应该是多少个字节数,而且这也与OS、编译器相关。
下面给出一个表格,方便查看各类型的字节数:
上图只是简单的基本数据类型的字节数:
在现实生活中,我们可以用 int类型 来表示有几个????。可以用 char类型表示'a'字符。可以用float来表示一个身高1.75米等。
当我们要表示一个数据集合的时候,我们可以用 int数组表示,一个班级中所有学生的数学成绩。
进而我们可能会用一个结构体struct,来表示我们的学校:学校的名字,学校地址。。。。。。
这里提到的结构体struct,和C语言的精髓,指针,在一起,可以很愉快的玩耍,比如可以形成链表......研究Objective-C的本质,也需要你了解struct和指针的相关知识。
进而.......
目前我们接触的许多编程语言都是类C的语言。Objective-C更是基于C/C++开发的.
事实上:我们平时所写的Objective-C代码,底层实现都是C/C++代码。
Objective-C的面向对象都是基于C/C++的数据结构实现的。
将OC代码转换成C/C++代码:
将main.c 转化为 main-arm64.cpp的指令如下:
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m -o main-arm64.cpp复制代码
我们发现,NSObject.h 头文件中, 有一个Class isa的成员变量
@interface NSObject <NSObject> { #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wobjc-interface-ivars" Class isa OBJC_ISA_AVAILABILITY; #pragma clang diagnostic pop }复制代码
在 main-arm64.cpp 文件中:
typedef struct objc_class *Class;复制代码
对比之后:可以这么说。
@interface NSObject{ Class isa ; } @end复制代码
底层被转换成了:
struct NSObject_IMPL { Class isa; };复制代码
由于:Class 的类型是指针:
typedef struct objc_class *Class;复制代码
在苹果arm64架构上,指针是 占用8个字节。
我们可以说 一个NSObject占用8个字节吗? 其实并不是8个字节。
NSObject *obj = [[NSObject alloc] init]; // 获得NSObject实例对象的成员变量所占用的大小 >> 8\ NSLog(@"%zd", class_getInstanceSize([NSObject class])); // 获得obj指针所指向内存的大小 >> 16 NSLog(@"%zd", malloc_size((__bridge const void *)obj));复制代码
通过class_getInstanceSize() // 获得 NSObject实例对象的成员变量所占用的大小。
通过 malloc_size() //获得obj指针所指向内存的大小。
通过测试发现:class_getInstanceSize() 获得的是 8个字节。 malloc_size() 获得的是16个字节。
NSObject *obj = [[NSObject alloc] init];复制代码
上述代码:产生了一个obj对象。他的内存是如何布局的?
由于isa是指针类型。按道理说obj 所占用得 8个字节的内存。
让我们进入到函数调用的内部一查究竟。
alloc函数的实现,我们可以在苹果开放的源码中查找
size_t instanceSize(size_t extraBytes) { size_t size = alignedInstanceSize() + extraBytes; // CF requires all objects be at least 16 bytes. if (size < 16) size = 16; return size; } // CF requires all objects be at least 16 bytes.复制代码
如果内存大小 < 16字节,都会给开辟16字节的空间大小。
在Xcode中找到 Debug --> Debug Workflow --> view Memory 查看内存:
其实我们通过Xcode 查看内存,也可以发现,确实是16个字节。只不过Class isa 成员变量,只使用了8个字节。但是内存确实是分配了16个字节。
所以:一个NSObject对象占用多少内存?
系统分配了16个字节给NSObject对象。但NSObject对象内部只使用了8个字节的空间(64bit环境下)可以通过class_getinstance函数获得。
接下来,看一下Student对象在内存中占用几个字节?
@interface Student : NSObject { @public int _no; int _age; } @end @implementation Student @end复制代码
将OC代码转为C/C++代码后,会发现
struct Student_IMPL { Class isa; int _no; int _age; };复制代码
Student *stu = [[Student alloc] init]; stu->_no = 4; stu->_age = 5; NSLog(@"%zd", class_getInstanceSize([Student class])); NSLog(@"%zd", malloc_size((__bridge const void *)stu));复制代码
通过测试发现:class_getInstanceSize() 获得的是 16个字节。 malloc_size() 获得的是16个字节。
通过Xcode工具查看内存:确实也是16个字节。
可以看到内存中的04 00 00 00 代表 4,即stu->_no, 内存中的05 00 00 00 代表 5,即stu->_age。
注意:04 00 00 00 为什么代表 4 , 请自己了解 大小端。 苹果arm64是小端。 大端模式定义是:大端模式就是低位存放在高地址上。高位存放在低地址上。
下面,我们看一下,更复杂的继承结构:
@interface Person : NSObject { @public int _age; } @end @implementation Person @end @interface Student : Person { int _no; } @end @implementation Student @end复制代码
将OC代码转化为C/C++代码:
struct NSObject_IMPL { Class isa; }; struct Person_IMPL { struct NSObject_IMPL NSObject_IVARS; // 8 int _age; // 4 }; // 16 内存对齐规则之一:结构体的大小必须是最大成员大小的倍数 struct Student_IMPL { struct Person_IMPL Person_IVARS; int _no; }; // 16复制代码
通过测试发现:class_getInstanceSize() 获得的是 16个字节。 malloc_size() 获得的是16个字节。
通过XCode查看内存,确实各占用16字节的内存。其中 Person对象占用16字节,是因为12<16, 所以实际应分配16字节的内存。前面已经讲述。 Student对象占用16字节是因为8+4+4 = 16。
前面我们讲了对象的成员变量所占内存。接下来我们看一下属性变量。
我们常说:OC中的属性会自动增加一个带_的成员变量。get及set方法。
正好我们从内存本质来看一下。会不会自动生成成员变量。
@interface Person : NSObject { @public int _age; } @property (nonatomic, assign) int height; @end @implementation Person @end OC转换成C/C++后: struct Person_IMPL { struct NSObject_IMPL NSObject_IVARS; int _age; int _height; };复制代码
我们发现,确实自动生成了_height,很好的证明了OC语法。
那么setHeight getHeight方法去哪里了?我们alloc出来的类实例对象只存在成员变量。
方法是不可能放在成员变量里的。
因为方法有一份就够了。
我们再使用Person类创建Person对象的时候,一般会创建多个对象。
方法不可能在每个实例里都有。只有成员变量,每个Person对象都会有一份。
其实,方法不可能存在成员变量里,只放在类对象方法列表里。
以后我们再来讨论。。。
重要:
如果你在日常的研究中发现。对象的内存占用和你的预期产生了出入,不要着急。以下进行说明。
创建对象时为对象开辟内存时调用 alloc方法 最终会调用calloc内存开辟函数。
在libmalloc库中,(苹果开源的代码中可以下载该库)你找到该库,找到这样一个宏定义。
#define NANO_MAX_SIZE 256 /* Buckets sized {16, 32, 48, 64, 80, 96, 112, ...} */复制代码
苹果在分配内存的时候,有内存对齐,这个内存对齐和结构体的内存对齐是两码事。
其实,对象内存都是16字节的倍数。
所以如果你看到你发现你预期的内存应该是24啊。怎么变成了32? 不要惊讶,苹果的iOS系统在给你做内存分配的时候,是有一定规则的,这有利于内存的管理。
作者:用户2109385917629
链接:https://juejin.cn/post/7020611634368348168