clang -rewrite-objc main.m -o main.cpp
NSObject是所有类的基类,所有的类都继承自它,它太平常了,平常到我们从不去多加任何思考,但是它又那么重要,因为他是OC的基础,所以今天我们将通过一下几点对NSObject
抽丝剥茧,看看他的底层到底是怎样的:
- 一个 NSObject 对象占用多少内存?
- 对象的 isa 指针指向哪里?
- OC 的类信息存放在哪里?
思考一下:一个 OC 对象在内存中是如何布局的?
我们创建一个简单 Command line 项目,然后通过xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m -0 main.cpp
命令把 .m 文件转换为 c++文件查看一下底层代码,然后在转换后的.cpp
文件中搜索struct NSObject_IMPL {
找到:
struct NSObject_IMPL {
__unsafe_unretained Class isa;
};
这就是NSObject
的底层实现,我们也可以直接进入到NSObject.h
的头文件中看看NSObject
是如何定义的:
@interface NSObject {
Class isa;
}
@end
可以发现NSObject.h
头文件的定义和.cpp
文件中都是一样的,NSObject
的底层就是一个 C++ 结构体,结构体中只有一个class
类型的isa
成员.这个class
又是什么类型呢?点进去看一下:
typedef struct objc_class *Class;
原来class
就是一个指向struct objc_class
结构体类型的指针!现在知道了Class
是一个指针,而NSObject
底层就只有一个Class isa
那我们就知道了NSObject
占用多少内存了.因为指针在64位系统中占8个字节,在32位系统中占4个字节.所以我么可以猜测:NSObject
在内存中占8个字节.我们猜测的正确吗?下面开始验证一下:
NSLog(@"NSObject 占用了 %zd 个字节?", class_getInstanceSize([NSObject class]));
//打印输出
NSObject 占用了 8 个字节?
难道我们的猜测是正确的?
注意:class_getInstanceSize()
是获取某一个类创建出来的实例对象所占用的内存大小.
系统中还有一个方法是取出一个指针所指向的内存的大小:malloc_size(<#const void *ptr#>)
运行一下代码:
NSObject *obj = [[NSObject alloc]init];
NSLog(@"NSObject 占用了 %zd 个字节?", class_getInstanceSize([NSObject class]));
NSLog(@"obj指针指向的内存占用了 %zd 个字节?",malloc_size((__bridge const void *)obj));
// 打印
NSObject 占用了 8 个字节?
obj指针指向的内存占用了 16 个字节?
好了,现在有两个结果:8 和 16.哪一个才一个 NSObject 对象占用多少内存?
的结果呢?答案是16个字节.因为class_getInstanceSize ()
返回的其实并不是一个对象的全部内存大小,实际上它返回的是一个类的实例对象的成员变量所占用的内存大小,我们可以通过 runtime 的源码看一下:
查看步骤:
- 打开 runtime 源码搜索
class_getInstanceSize
- 找到
size_t class_getInstanceSize(Class cls)
{
if (!cls) return 0;
return cls->alignedInstanceSize();
}
- 点击进入
alignedInstanceSize
:
// Class's ivar size rounded up to a pointer-size boundary.
翻译:返回的是class的ivar大小
uint32_t alignedInstanceSize() {
return word_align(unalignedInstanceSize());
}
可以看到, class_getInstanceSize()
的确返回的是实例对象的成员变量锁占用的大小.其实 class_getInstanceSize
和 malloc_size
的关系就好比下图:
所以正确的说法应该是:
一个 NSObject 对象占用多少内存❓
系统分配了 16 个字节给NSObject
对象(通过malloc_size
可获得),但NSObject
对象内部只是用了 8 个字节的空间(在64位环境下可通过class_getInstanceSize
函数获得).