问题1:一个NSObject对象占用了多少内存?
可以通过方法malloc_size来查看某个对象分配了多少内存
1 | #import <Foundation/Foundation.h> |
输出
1 | 16 |
为什么会是16呢??
我们都知道oc的代码经过编译以后都会生成C或者是C++代码,再到汇编,最后到机器码,通过以下代码查看下编译成c++以后的代码的样子,为了准确指定下架构和设备
1 | xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m |
成功后在同层目录下出现一个main.cpp文件
1 | ➜ OCObjeDemo tree |
在main.cpp中搜索NSObject会找到以下代码
1 | struct NSObject_IMPL { |
IMPL就是@implementation简写,同时,在NSObject的头文件中会看到
1 | @interface NSObject <NSObject> { |
可以看出NSObject就是一个结构体类型,并且只有一个isa变量,到这里可能会以为之前输出的16应该就是这里isa所占用的字节数,然而事实并不是这样的,通过runtime的一个方法class_getInstanceSize可以证明这个结论,我们修改main.m代码如下
1 | #import <Foundation/Foundation.h> |
输出
1 | 8 |
class_getInstanceSize这个方法需要解释一下打开苹果开源代码中的objc4-723.tar.gz,搜索class_getInstanceSize可得
1 | size_t class_getInstanceSize(Class cls) |
就是获取某个类的所有成员变量的内存对齐后的地址大小
回到正题,既然只有一个元素并且这个元素的所占的大小是8个字节,为什么malloc_size会输出16呢,这就要看下NSObject的alloc方法的实现了
1 | + (id)alloc { |
一路点下去会看到以下代码
1 | size_t instanceSize(size_t extraBytes) { |
看到这里也就解释了为什么是输出16而不是8了,也就是NSObject最少都应该是16个字节大小的,想想应该是框架需要吧
问题2:NSObject子类的内存如何分配的
将main.m改造如下,新增Animal类
1 | #import <Foundation/Foundation.h> |
输出
1 | 16 |
同样使用命令
1 | xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m |
搜索Animal
1 | struct Animal_IMPL { |
由上面可知NSObject_IMPL为NSObject的内存结构
1 | struct NSObject_IMPL { |
从而Animal可以改为下面的形式
1 | struct Animal_IMPL { |
这也就解释了为什么2个都输出16了,还可以通过xcode的断点调试下
通过 p + 需要打印的对象,获取对象的地址

再 Debug -> Debug Workflow -> View Memory


还可以通过memory write + 地址 + 值 修改值,下面的是吧_age的值由原来的5改成了97
1 | memory write 0x100636308 61 |
1 . 0x100636308 是变量_age所在的地址,它占4个字节,1个字节8位,1个十六进制表示4位,2个正好一个字节,0x100636300为结构体的起始地址,往右数8位正好是0x100636308;
61是十进制97的ASCII值

问题3:多重继承又该如何分配内存
再次修改main.m代码
1 | #import <Foundation/Foundation.h> |
增加Dog继承Animal
输出
1 | 16 |
同样查看main.cpp
1 | struct Dog_IMPL { |
相当于
1 | struct Dog_IMPL { |
isa占8个字节 , _age和 _number各占4个字节,对象一共分配malloc_size16个字节,成员变量class_getInstanceSize分配16个字节
补充说明
通过一个简单的例子证明下结构体的地址和首元素的地址相同
1 | int main(int argc, const char * argv[]) { |
都输出
1 | 0x7ffeefbff518 |