一、一个NSObject对象占用多少内存?
- 针对问题, 我们创建一个项目工程, 并创建一个NSObject对象

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

- 所以Objective-C的面向对象都是基于C\C++的数据结构实现的
思考: Objective-C的对象、类主要是基于C\C++的什么数据结构实现的?
结构体: Objective-C中类的属性多样, 只有结构体能承载
- 将Objective-C代码转换为C\C++代码, 使用Mac终端进行转换, C++的文件格式是
.cpp
$ clang -rewrite-objc main.m -o main.cpp

- 使用Xcode查看
main.app文件, 可以看到转换后的文件有9万多行代码, 并且我们创建NSObject对象的代码就在文件的最后面

-
注意: 在不同平台上, 支持的代码是不一样的, 比如Windows, Mac, iOS等平台
-
所以在转换代码的时候, 可以指定某一个平台后再转换, 终端指令如下
# 指定: iOS操作系统, arm64架构, 将 main.m 文件转为 main-arm64.cpp 文件
$ xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m -o main-arm64.cpp

- 使用Xcode查看
main-arm64.cpp文件, 此时转换后的C++代码只有3万多行, 创建NSObject对象的代码同样在最底部

- 此时的项目工程有10个错误, 这些错误全都是
main-arm64.cpp文件带来的, 所以可以使其不参与编译, 将错误消除

- 在
main-arm64.cpp文件中, 找到下面的代码

-
NSObject_IMPL实际意思是NSObject Implementation, 即: NSObject类的实现 -
我们可以使用
command + 鼠标右键点击NSObject类, 进入NSObject.h文件中查看NSObject类的定义

通过这种方法, 可以从侧面证明 Objective-C 中的类, 在底层是C\C++中的结构体类型
所以NSObject对象, 在内存中就是一个结构体对象
-
思考: 一个OC对象在内存中是如何布局的?
-
NSObject的底层实现如下:

- 由图可知, 一个
NSObject_IMPL结构体中, 只有一个成员变量Class isa, 我们使用command + 鼠标右键点击Class类型查看一下

-
由图可知,
Class类型是一个指针, 所以NSObject_IMPL结构体中只包含一个指针变量 -
在
arm64架构中, 一个地址占用8个字节, 所以isa指针占用8个字节的内存空间 -
可以使用
runtime中的class_getInstanceSize(Class _Nullable cls)方法, 查看一个对象中, 所有成员变量占用的内存大小

-
那么一个NSObject对象, 到底占用多少的内存空间呢?
-
可以使用
malloc框架中的malloc_size(const void *ptr)方法查看

-
疑问: 为什么NSObject对象在内存中只使用了8个字节存储
isa指针, 却分配了16个字节的内存空间呢? -
在官网中, 找到
Objc4开源代码

- 下载最新代码

- 使用Xcode打开
Objc4, 查找alloc方法的底层方法_objc_rootAllocWithZone()

- 查看
_objc_rootAllocWithZone()方法的实现, 可以找到class_createInstance()方法, 这个就是创建对象调用的方法

- 继续查看
class_createInstance()方法的实现, 方法中调用了_class_createInstanceFromZon()方法

- 继续查看
_class_createInstanceFromZon()方法的实现, 此时可以看到创建对象时调用calloc()函数,calloc()函数传入的第二个参数size, 就是分配内存的大小,size是通过instanceSize()方法获取的

- 继续查看
instanceSize()方法, 可以知道, size的最小是就是16

- 这就是为什么NSObject明明只有一个指针变量, 却占用了16个字节内存的原因
一个NSObject对象占用多少内存?
系统分配了16个字节给NSObject对象 (通过malloc_size函数获得)
但NSObject对象内存只使用了8个字节的空间(64bit环境下, 可以通过class_getInstanceSize函数获得)
二、窥探NSObject的内存
- 运行程序, 打断点, 打印obj对象, 获取obj对象的地址

- 查看内存

- 输入上面找到的obj地址, 查看obj对象在内存中的信息, 其中isa使用了8个字节, 剩余8个字节没有被使用

三、常用LLDB指令
1、print、p: 打印值

2、po: 打印对象

3、memory read/数量格式字节数 内存地址: 读取内存, 有缩略写法: x

- 读取内存的格式和字节数如下:
格式:
x: 16进制 f: 浮点数 d: 十进制
字节大小:
b: byte: 1字节 h: half word: 2字节
w: word: 4字节 g: giant word: 8字节
- 指定条件读取内存

4、memory write 内存地址 数值: 修改内存中的值

四、自定义类型的实例, 在内存中的大小
1、以自定义类Student为例
- 定义
Student类, 继承自NSObject, 并添加两个int类型的成员变量, 具体实现如下:
@interface Student: NSObject
{
@public
int _no;
int _age;
}
@end
@implementation Student
// 类的实现部分
@end
- 此时测试代码如下:

2、根据main.m文件, 获取对应的.cpp文件, 查看Student在底层的具体实现
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m -o main-arm64.cpp
- 查看
Student的底层结构体实现:

Student_IMPL中struct NSObject_IMPL NSObject_IVARS;是父类NSObject的实现

- 所以
Student_IMPL的实际实现如下:
struct Student_IMPL {
Class isa;
int _no;
int _age;
};
3、测试Student的实例, 是不是Student_IMPL类型
- 使用
struct Student_IMPL *指针, 指向Student的一个实例, 代码如下
Student *stu = [[Student alloc] init];
stu->_no = 4;
stu->_age = 5;
struct Student_IMPL *stuImpl = (__bridge struct Student_IMPL*)stu;
NSLog(@"no - %d, age - %d", stuImpl->_no, stuImpl->_age);
- 运行后, 可以打印出
_no和_age的值, 就是4和5:
// 打印结果
no - 4, age - 5
-
这从侧面可以验证
Student底层就是Student_IMPL类型 -
也可以在下图的位置打断点, 用来查看
Student在内存中的数据

- 可以看到内存中的
Student实例:

- 前八个字节是
isa, 后八个字节是_no和_age, 因为一个int类型只占4个字节 - 在结构体中, 成员变量之间的地址是连接在一起的, 所以
Student的成员变量一共占用16个字节的内存 - 由上面可知一个
NSObject的实例最少分配16个字节, 所以例子中定义的Student类型的一个实例, 在内存中会占用16个字节的内存, 并且这个16个字节全部都被使用 - 使用
class_getInstanceSize和malloc_size进行测试

五、更复杂的继承结构
- 现有如下代码:
Person继承自NSObject, 有一个成员变量_age,Student继承自Person, 有一个成员变量_no
@interface Person: NSObject
{
int _age;
}
@end
@implementation Person
@end
@interface Student: Person
{
int _no;
}
@end
@implementation Student
@end
- 此时底层实现如下:
struct NSObject_IMPL {
Class isa;
};
struct Person_IMPL {
struct NSObject_IMPL NSObject_IVARS;
int _age;
};
struct Student_IMPL {
struct Person_IMPL Person_IVARS;
int _no;
};
-
可以推测出:
Person的实例占用内存为isa + _age = 12, 小于16, 所以Person的实例占用16字节的内存Student的实例占用内存为isa + _age + _no = 16, 等于16, 所以Student的实例占用16字节的内存
-
使用代码检测:

注意:
我们知道class_getInstanceSize函数获取到的, 是类型中所有成员变量一共占用内存的大小, 所以Person获取到的成员变量应该是isa+_age=12个字节, 但是为什么返回16字节呢?
- 查看一下
class_getInstanceSize的源码:

- 进入
alignedInstanceSize函数:

实际上
class_getInstanceSize获取到的大小, 是内存对齐之后的大小, 即所有成员变量中, 占用内存最大的那个成员变量的倍数,Person内的NSObject_IMPL有一个isa占用8个字节,_age占用4个字节, 所以class_getInstanceSize获取到的是8的倍数, 即16个字节
-
对于
Person,NSObject_IMPL虽然占有16个字节, 但是只有一个成员变量isa, 所以有8个字节是空的, 所以, 会将_age放在空的位置, 而不是继续累加至20个字节 -
对于
Student, 有两个成员变量Person_IMPL和_no,Person_IMPL占用16个字节, 但是有4个字节是空的, 所以分配给了_no使用, 即Student分配内存是16个字节
1、属性会生成对应的成员变量
- 再次给
Student添加一个成员属性height
@interface Student: Person
{
int _no;
}
@property (nonatomic, assign) int height;
@end
@implementation Student
@end
- 此时
Student的底层实现为:
struct Student_IMPL {
struct Person_IMPL Person_IVARS;
int _no;
int _height;
};
- 测试
Student在内存中的大小如下:

- 此时
Student中的成员变量是isa + _age + _no + _height = 20, 最大成员变量isa占用内存为8, 所以class_getInstanceSize获取到的是24个字节
注意:
OC中给实例对象分配空间时, 是按照16, 32, 48, 64, 80, 96...按照16的倍数递增的, 所以malloc_size函数获取到的Student实例内存是32