问题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_size
16个字节,成员变量class_getInstanceSize
分配16个字节
补充说明
通过一个简单的例子证明下结构体的地址和首元素的地址相同
1 | int main(int argc, const char * argv[]) { |
都输出
1 | 0x7ffeefbff518 |