今天来聊聊NSObject对象占用多少内存的话题。
一.什么是NSObject对象?
NSObject *obj = [[NSObject alloc] init];这句代码中,obj是对象么?NONONO……很多人有这个误解,认为这个obj就是对象。正解:[[NSObject alloc] init]这个才是对象,obj是个指针,也就是obj指针指向的那片内存空间叫做对象。
二.对象本质是什么?
OC的底层是C和C++来实现的,那么可以猜测一下,这样一个复杂的的可以包含各种数据类型的集合,所对应的C的数据结构是什么呢?数组?字典?集合?结构体?
我们不烦大胆猜测一下……思索过后发现只有结构体大体符合,那么咱们来验证一下。
将含有下面的代码的main.m转成C++文件(main.cpp)
(定位到main.m,在终端输入指令xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m -o main-arm64.cpp)
int main(int argc, const char * argv[]) {
@autoreleasepool {
NSObject *obj = [[NSObject alloc] init];
}
return 0;
}
在main.cpp文件中发现了这样的一个结构体:
struct NSObject_IMPL {
Class isa;
};
看见isa指针,就觉得好亲切。这说明了两个问题:
- 每个对象的底层都是一个结构体
- 每个对象内部都有一个isa指针
那么问题来了,64位下指针占用8个字节,那是不是一个对象占用8个字节的内存空间呢?答案显然不是。因为还有我们的成员变量啊,所以到此为止,得出结论:一个对象至少需要占用8个字节的内存空间来存放isa指针。
此处介绍两个计算内存空间大小的方法:
- runtime 中的class_getInstanceSize()
*(return The size in bytes of instances of the class,返回一个类的实例对象的大小,其实就是底层结构体的大小) - malloc_size()
(获得对象所开辟空间的大小)
由上图可以看出,结构体大小为8个字节,但是系统给它分配了16个字节。
三.自定义一个继承于NSObject的类,研究实例对象的内存空间
1.场景一
@interface Dog :NSObject{
@public
int _age;
int _height;
}
@end
新建一个Dog类继承于NSObject,按上述操作转化成c++代码。得到:
struct Dog_IMPL {
struct NSObject_IMPL NSObject_IVARS;
int _age;
int _height;
};
猜测:struct NSObject_IMPL 结构体里边只有一个isa指针,占用8个字节。int类型在64位下,占用4个字节。那么这个Dog对象占用8+4+4=16个字节。
提醒:在计算的时候要注意一个问题:结构体的内存对齐
我们分别用class_getInstanceSize()和malloc_size()方法验证一下。
2.场景二
给Dog类在添加一个int成员变量,如下
@interface Dog :NSObject{
@public
int _age;
int _height;
int _weight;
}
猜测:struct NSObject_IMPL 结构体里边只有一个isa指针,占用8个字节。int类型在64位下,占用4个字节。根据内存对齐原则,那么这个Dog对象占用8+4+4+8=24个字节。
继续用上述方法,测试内存,如下
疑问:为什么会出现32呢?
解答:根据上边对两个计算内存方法的解释:
class_getInstanceSize():说白了就是计算结构体所占内存的大小;
malloc_size():计算对象所开辟的内存空间的大小;
一个是理论计算,一个是实际大小。为什么会出现这样的差距呢?这就是系统框架的限制。此处不做深入研究。
四.方法的内存都跑到哪里去了?
对于这个东西,请大家看我的另一篇对于oc方法的论述。现在只需要知道,一个实例(instance)对象中不存储方法。
传送门:
五.总结
结论:
1.一个NSObject对象,系统分配了16个字节的空间,但是实际上用了8个字节的空间
2.一个实例对象的内存空间=它的底层结构体的内存大小+系统框架给的限定
3.实例对象的内存中不包括方法的内存