NSObject的本质
1 2 3 4 5 6 7 8 9 10
| #import <Foundation/Foundation.h> int main(int argc, const char * argv[]) { @autoreleasepool { NSObject *obj = [[NSObject alloc] init]; NSLog(@"Hello, World!"); } return 0; }
|
以上一段代码使用如下一段命令进行rewrite后,会被改为c++代码,生成main.cpp
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m
1 2 3 4 5 6 7
| int main(int argc, const char * argv[]) { { __AtAutoreleasePool __autoreleasepool; NSObject *obj = ((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("alloc")), sel_registerName("init")); NSLog((NSString *)&__NSConstantStringImpl__var_folders_s0_4wsgyhps0fv7r90xwgjm4lwr0000gn_T_main_96c2d6_mi_0); } return 0; }
|
NSObject 的结构为
1 2 3 4 5
| typedef struct objc_class *Class; struct objc_object { Class _Nonnull isa __attribute__((deprecated)); }; typedef struct objc_object NSObject;
|
可以看到NSObject是由结构体objc_object来定义的,objc_object里面只有一个Class类型的isa属性,指向该类的类信息;Class是由结构体 objc_class来定义。由此可以知道oc编写的代码都会被翻译为C++代码。
NSObject的内存占用
class_getInstanceSize
是一个runtime提供的API,用于获取类实例对象所占用的内存大小,源码如下:
1 2 3 4 5 6 7 8 9 10
| size_t class_getInstanceSize(Class cls) { if (!cls) return 0; return cls->alignedInstanceSize(); }
uint32_t alignedInstanceSize() const { return word_align(unalignedInstanceSize()); }
|
根据源码的意思来看, class_getInstanceSize
就是获取实例对象中成员变量内存大小。
下面我们使用一个case来说明一个对象占用内存的大小:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| #import <Foundation/Foundation.h> #import <objc/runtime.h> #import <malloc/malloc.h>
int main(int argc, const char * argv[]) { @autoreleasepool { NSObject *obj = [[NSObject alloc] init]; size_t class_size = class_getInstanceSize(NSObject.class); void *ptr = (__bridge void *)obj; size_t ptrmalloc_size = malloc_size(ptr); NSLog(@"class_size:%zu ptrmalloc_size:%zu", class_size, ptrmalloc_size); } return 0; }
|
运行结果为
class_size:8 ptrmalloc_size:16
运行结果说明nsobject类对象的大小正好是isa指针的大小为8个字节,使用view memory可以查看ptr指向的内存,占用16个字节。
这是因为在objc的alloc内存申请的流程中有这样一段代码,限制一个NSObect对象的大小至少要占用16个字节。
1 2 3 4 5 6 7 8 9 10
| size_t instanceSize(size_t extraBytes) const { if (fastpath(cache.hasFastInstanceSize(extraBytes))) { return cache.fastInstanceSize(extraBytes); }
size_t size = alignedInstanceSize() + extraBytes; if (size < 16) size = 16; return size; }
|
所以我们知道NSObject中只有一个isa指针,所以NSObject类对象占用8个字节,NSObject在内存中占用16个字节。
自定义类的内存占用
定义Test类,并进行如下初始化,那么obj在内存中是怎样布局的呢?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| @interface Test : NSObject
@property(nonatomic, assign) int property_1; @property(nonatomic, assign) int property_2;
@end
@implementation Test @end
int main(int argc, const char * argv[]) { @autoreleasepool { Test *obj = [[Test alloc] init]; obj.property_1 = 1; obj.property_2 = 2; size_t class_size = class_getInstanceSize(Test.class); void *ptr = (__bridge void *)obj; size_t ptrmalloc_size = malloc_size(ptr); NSLog(@"class_size:%zu ptrmalloc_size:%zu", class_size, ptrmalloc_size); } return 0; }
|
运行的结果如下
class_size:16 ptrmalloc_size:16
使用view memory查看ptr指向的内存,如下图
其中红框是对象属性property_1和property_2 的值,分别是1和2,int类型各占4个字节。
如果Test的定义如下
1 2 3 4 5 6 7
| @interface Test : NSObject
@property(nonatomic, assign) int property_1; @property(nonatomic, assign) int property_2; @property(nonatomic, assign) int property_3;
@end
|
初始化obj并赋值
1 2 3 4
| Test *obj = [[Test alloc] init]; obj.property_1 = 1; obj.property_2 = 2; obj.property_3 = 3;
|
运行结果为
class_size:24 ptrmalloc_size:32
则在内存中的布局如下,可以看到property_1、property_2和property_3 的值分别为1、2、3
首先Test的类对象的大小为24,但是为何不是 8(isa指针) + 三个属性占用的12个字节 = 20 个字节?
这是因为表示Class的struct objc_class是一个结构体,结构体大小结果要为成员中最大字节的整数倍,所以Test类指向的objc_class变量大小为24。
对象在内存中占用了32个字节,这是因为malloc要满足 16 字节对齐原则 ( 可以在 libmaclloc 源码查找到 ) , 因此实际总占用内存为24. 而实际开辟则满足对齐标准开辟为 32.
参考
https://juejin.cn/post/6844903939985391629
https://zhuanlan.zhihu.com/p/98432137