0%

一个NSObject对象占用多少内存

一个NSObject对象占用多少内存

我们平时所编写的Objective-C代码,底层实现都是C/C++代码,

所以OC的面向对象都是基于C/C++的数据结构实现的

思考:OC对象主要是基于C/C++的什么数据结构实现的呢???

想要了解OC对象主要是基于C/C++的什么数据结构实现的,我们首先要做的就是将Objective-C代码转化为C/C++代码,这样我们才能清楚的看清是怎么实现的

然后我们打开终端,在命令行找到cd到文件目录,然后中输入:

1
2
clang -rewrite-objc main.m 

命令可以将main.m编译成C++的代码,改成不同的文件名,就会生成不同的c++代码
这是就生成了main.cpp这个c++文件,打开文件代码
查看该main.cpp最底下的main函数,

但是不同平台支持的代码肯定是不一样的,像平台有WindowsmaciOS,架构有模拟器(i386)、32bit(armv7)、64bit(arm64),我们使用iOS,他的架构现在基本上都是64bit(arm64)

1
2
xcrun  -sdk  iphoneos  clang  -arch  arm64  -rewrite-objc OC源文件  -o  输出的CPP文件
如果需要链接其他框架,使用-framework参数。比如-framework UIKit

在终端输入命令以后,我们会生成一个main.cpp文件,打开main.cpp文件文件,我们把main.cpp文件拉到最下面,我们会看到这样一段代码

1
2
3
4
5
6
7
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;


}
return 0;
}

这一段代码就是我们OC代码中的main函数的实现

1
2
3
4
5
6
7
int main(int argc, const char * argv[]) {
@autoreleasepool {

}
return 0;
}

这时我们在main函数写入这一段代码,然后我们点击进入,查看代码实现

1
NSObject *obj = [[NSObject alloc] init];

点击NSObject进入内部,可以看到NSObject底层实现

1
2
3
struct NSObject {
Class isa;
};

我们用NSObject_IMPL查找在c++文件中具体的实现

1
2
3
struct NSObject_IMPL {
Class isa;
};

我们再一次执行命令

1
xcrun  -sdk  iphoneos  clang  -arch  arm64  -rewrite-objc main.m

生成的C++代码为

1
2
3
4
5
6
7
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __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"));

}
return 0;
}

有两个方法可以打印内存大小

1
2
3
4
5
// 获得NSObject实例对象的成员变量所占用的大小  
NSLog(@"%zd", class_getInstanceSize([NSObject class]));

// 获得obj指针所指向内存的大小
NSLog(@"%zd", malloc_size((__bridge const void *)obj));

打印结果

一个OC对象在内存中是怎么样布局的呢

我们在C++文件中找到NSObject的实现
OC代码

1
2
3
struct NSObject {
Class isa;
};

c++代码

1
2
3
struct NSObject_IMPL {
Class isa;
};

我们知道一个指针是8个字节,但是NSObject对象打印16个字节,他们是怎么样布局的呢

我们可以根据内存地址实时查看内存分配情况Debug -> Debug Workfllow -> View Memory (Shift + Command + M)

菜单选项如何查看内存:

输入内存地址:

通过Xcode查看内存数据:

通过LLDB命令查看内存数据:

我们也可以直接使用 LLDB命令来查看内存地址
常用LLDB命令

  • print、p:打印

  • po:打印对象

  • 读取内存

    • memory read/数量格式字节数 内存地址
    • x/数量格式字节数 内存地址(格式:x是16进制,f是浮点,d是10进制;字节大小
      :b:byte 1字节,h:half word 2字节,w:word 4字节,g:giant word 8字节)
  • 修改内存中的值(memory write 内存地址 数值 memory write 0x0000010 10)

问题1:假设我创建一个Animal类,里面有age,weight两个属性,那么他的内存是多大呢?

1
2
3
Animal *animal = [[Animal alloc] init];
animal.age = 10;
animal.weight = 20;

我们先执行命令,查看一下c++源码

1
2
3
4
5
struct Animal_IMPL {
struct NSObject_IMPL NSObject_IVARS;
int _age;
int _weight;
};

我们在知道结果之前大概猜猜内存是多大呢?16,24,32…

猜16字节的猜对了,我们先看看结果

我们用LLDB命令打印一下

1
2
3
4
5
6
7
(lldb) po animal
<Animal: 0x1005c23c0>

(lldb) memory read 0x1005c23c0
0x1005c23c0: 81 13 00 00 01 80 1d 00 0a 00 00 00 14 00 00 00 ................
0x1005c23d0: a0 24 5c 00 01 00 00 00 e0 26 5c 00 01 00 00 00 .$\......&\.....
(lldb)

为什么会是0a 00 00 0014 00 00 00呢,而不是00 00 00 0a00 00 00 14,这个就要考虑大端小端,具体概念自己可以去查。

但是为什么会是16个字节呢,因为int类型占用4个字节,两个int类型8个字节,一个isa8个字节,因为刚刚占满16个字节,对象就没有在开辟新的空间了

如果在多一个feetCount会占用几个字节呢

占用32个字节,大家是不是很惊讶,没有猜到

其实这又要提到一个新的知识点了内存对齐,我们知道OC对象就是C++结构体,而结构体的大小必须是最大成员大小的倍数,当在多了一个feetCount以后,内存不够用了,然后就需要扩展了。

如果是这样呢,占用内存是多少

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@interface Animal : NSObject

@property (nonatomic, assign) int age;
@property (nonatomic, assign) int weight;

@end

@implementation Animal

@end

@interface Cat : Animal
@property (nonatomic, assign) int feetCount;
@end

@implementation Cat

@end

Cat继承自Animal

我们生成C++代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
struct Cat_IMPL {
struct Animal_IMPL Animal_IVARS;
int _feetCount;
};

struct Animal_IMPL {
struct NSObject_IMPL NSObject_IVARS;
int _weight;
int _age;
};

struct NSObject_IMPL {
Class isa;
};

整理一下就是这样

1
2
3
4
5
6
struct Cat_IMPL {
Class isa;
int _weight;
int _age;
int _feetCount;
};

参考demo