1.獲取 NSObject 對(duì)象占用的空間
- 創(chuàng)建一個(gè) NSObject 對(duì)象, 分別調(diào)用 class_getInstanceSize 和 malloc_size 方法
NSObject *obj = [[NSObject alloc] init];
// 需要導(dǎo)入 #import <objc/runtime.h>
NSLog(@"%zd", class_getInstanceSize([NSObject class]));
// 需要導(dǎo)入 #import <malloc/malloc.h>
NSLog(@"%zd", malloc_size((__bridge const void *)(obj)));
- 得到輸出:
2019-11-05 15:10:22.210505+0800 Interview001-OC對(duì)象的本質(zhì)[9215:816956] 8
2019-11-05 15:10:22.210675+0800 Interview001-OC對(duì)象的本質(zhì)[9215:816956] 16
可以看出系統(tǒng)開辟了16個(gè)字節(jié), 但是該對(duì)象只是用了8個(gè)字節(jié)
這是為什么了?點(diǎn)進(jìn) NSObject 可以看到下面的代碼, NSObject 對(duì)象真正的是一個(gè)結(jié)構(gòu)體指針, 所以會(huì)占用8個(gè)字節(jié)
/// An opaque type that represents an Objective-C class.
typedef struct objc_class *Class;
@interface NSObject <NSObject> {
Class isa ;
}
- 在 https://opensource.apple.com/tarballs/ 找到 runtime 源碼, 搜索 class_getInstanceSize 可以找到下面的代碼, 可以看出 class_getInstanceSize 獲取的是類的成員變量的 size, 并不是一個(gè)對(duì)象所占用的空間
size_t class_getInstanceSize(Class cls)
{
if (!cls) return 0;
return cls->alignedInstanceSize();
}
// Class's ivar size rounded up to a pointer-size boundary.
uint32_t alignedInstanceSize() {
return word_align(unalignedInstanceSize());
}
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;
}
- 那為什么會(huì)分配16個(gè)字節(jié)了? 也是在 runtime 里找到了答案, 當(dāng) size < 16 時(shí), 會(huì)默認(rèn)分配16個(gè)字節(jié)
可以參照 這篇文章 把.m文件編譯成.cpp文件, 查看 NSObject真正的實(shí)現(xiàn), 可以理解是對(duì)應(yīng) c++ 里的結(jié)構(gòu)體
我的 GitHub 里有已經(jīng)編譯后的.cpp文件 大家可以查看
2. 自定義對(duì)象占用的空間
自定義一個(gè) Person類
@interface Person : NSObject
{
}
@end
@implementation Person
@end
- 打印當(dāng)前對(duì)象
Person *p = [[Person alloc] init];
NSLog(@"%zd", class_getInstanceSize([Person class]));
NSLog(@"%zd", malloc_size((__bridge const void *)(p)));
得到的結(jié)果和 NSObject 對(duì)象一樣的
2019-11-05 15:30:11.991698+0800 Interview001-OC對(duì)象的本質(zhì)[9360:864510] 8
2019-11-05 15:30:11.991704+0800 Interview001-OC對(duì)象的本質(zhì)[9360:864510] 16
- 給自定義對(duì)象添加1個(gè)成員變量, 再次打印
@interface Person : NSObject
{
int _age;
}
@end
2019-11-05 15:31:56.767972+0800 Interview001-OC對(duì)象的本質(zhì)[9385:868476] 16
2019-11-05 15:31:56.767978+0800 Interview001-OC對(duì)象的本質(zhì)[9385:868476] 16
- 再次添加成員變量, 再次打印
@interface Person : NSObject
{
int _age;
int _number;
}
@end
2019-11-05 15:34:16.503491+0800 Interview001-OC對(duì)象的本質(zhì)[9414:874058] 16
2019-11-05 15:34:16.503497+0800 Interview001-OC對(duì)象的本質(zhì)[9414:874058] 16
- 很奇怪, 沒有變化啊? 我們編程成.cpp再來分析
struct Person_IMPL {
struct NSObject_IMPL NSObject_IVARS;
int _age;
int _number;
};
-
這時(shí)自定義對(duì)象包含了
- 1個(gè)結(jié)構(gòu)體指針 (8字節(jié))
- 1個(gè) int _age (4字節(jié))
- 1個(gè) int _number (4字節(jié))
8 + 4 + 4 = 16 字節(jié)
- 沒有添加任何成員變量時(shí), 只有1個(gè)結(jié)構(gòu)體指針, 所以會(huì)是8字節(jié)
- 添加1個(gè)成員變量時(shí), 有1個(gè)結(jié)構(gòu)體指針, 和1個(gè) int 類型的值, 那就是 8 + 4 = 12, 為什么 class_getInstanceSize 得到的結(jié)果是16了. 其實(shí)擴(kuò)容基數(shù)是8(內(nèi)存對(duì)齊原因, 結(jié)構(gòu)體的大小必須是最大成員大小的倍數(shù)), 8個(gè)字節(jié)不夠了, 就開辟了8*2個(gè)字節(jié), 依此類推
- 添加了2個(gè)成員變量時(shí), 有1個(gè)結(jié)構(gòu)體指針, 和2個(gè) int 類型的值,那就是 8 + 4 + 4 = 16
- 再添加一個(gè)成員變量
@interface Person : NSObject
{
int _age;
int _number;
int _sex;
}
@end
2019-11-05 15:48:24.959113+0800 Interview001-OC對(duì)象的本質(zhì)[9511:896453] 24
2019-11-05 15:48:24.959120+0800 Interview001-OC對(duì)象的本質(zhì)[9511:896453] 32
可以看出 class_getInstanceSize 已經(jīng)是8*3=24字節(jié)了
但是 malloc_size 卻是 32了, 因?yàn)槭且?6為基數(shù)來擴(kuò)容的, 因?yàn)?iOS 在分配對(duì)象內(nèi)存時(shí), 都是以16倍數(shù)來分配的
3.結(jié)論
- 1個(gè) NSObject 對(duì)象占用了16個(gè)字節(jié)
- 1個(gè)自定義對(duì)象占用了幾個(gè)字節(jié), 需要有多少成員變量, 同時(shí)還要計(jì)算上 NSObject 的 isa 指針大小, 同時(shí)為了對(duì)齊, 必須是16的倍數(shù)
4.注意點(diǎn)
- 如果自定義類有繼承關(guān)系, 如 Student 繼承于 Person, 要繼續(xù)父類的成員變量
- 注意內(nèi)存對(duì)齊問題, iOS 分配內(nèi)存問題
參考:
http://www.itdecent.cn/p/c22279cba38d
https://juejin.im/post/5abdd56df265da2396127e6b
https://www.iteye.com/blog/jakend-1839987
https://github.com/ludx/The-Lost-Art-of-C-Structure-Packing