NSObject的编译过程:
- 我们平时编写的oc代码,底层实现其实都是C/C++代码:
Objective-C > C/C++ > 汇编语言 > 机器语言代码- Objective-C对象主要是基于C/C++的结构体来实现的.
OC代码转成C++代码:
我们需要借助转换后的代码来分析
终端命令:
xcrun -sdk iphoneos clang -arch arm64 -rewrite-obj xxx.m -o xxx.cpp
不指定平台与架构:clang -rewrite-obj xxx.m -o xxx.cpp
iphoneos代表要编译的平台,-arch arm64代表平台架构 xxx代表文件名,-o为输出文件;如果报缺少某个库,加参数:-framework 库名
NSObject的Implementation:
参考:苹果源码 、刚才转换出来的xxx.cpp文件 、GNU源码
借助转换后后C++代码来分析,以下转换后的关键代码:
1 | //NSObject 定义:也就是说NSObject其实是一个isa指针,指向Class, |
相关问题一:NSObject占用多少个字节
从上面的打印可知,Class isa本身占用8个字节,NSObject *obj占16个字节,为什么占16个呢?
- 进一步分析证明:创建一个继承自
NSObject
带有成员变量的Student
对象,如下:- 猜想:按上面的结论一个
NSObject
实例对象占用16个字节,那下面可能这个Student
实例对象应该占用4+4+16 = 24; - 实际:是占用了16个字节, 因为8+4+4=16,没有超过16,也就相当于NSObject的16个字节,有8个其实像是预留的,具体看下面的代码结论。
- 猜想:按上面的结论一个
1 | //申明一个继承自NSObject对象的Student |
- 从NSObject源码分析:
1 | // objc-runtime-new.h |
结论:
- 系统分配了16个字节给NSObject对象(通过
malloc_size()
函数获得对应对象所指向内存中的大小)- 在64bit环境下,NSObject对象内部只使用了8个字节的空间(通过
class_getInstanceSize()
函数获得实例对象的大小),还有8个字节是预留的;- 如果结构体大小小于16个字节时,底层会最小按16来分配,超过16个字节后, 结构体的大小必须是最大成员大小的倍数(如Student结构体中最大为8,如Student超过16后,则会按8的倍数去分配);
- 但在最终分配大小的时候iOS系统做了内存分配的优化,及内存对齐机制,注意这里所说的内存对齐不是指结构体的那个内存对齐,最终分配给结构体的大小为16的倍数(可以从苹果的libmalloc源码中查看,如下图);
- 整体逻辑跟iOS本身的内存对齐机制、及运行机制有关;
其它查看内存大小的方式
- 通过xcode的工具来辅助性的查看:Xcode > Debug > Debug Workflew > View Memory > 然后在内存地址栏中输入你要查询的内存地址;
- 通过LLDB命令来打印内存地址:
(lldb) memory read 0x10331a920
也可以简写成:x 0x10331a920
;- 指定的格式来打印内存地址:
(lldb) x/3xg 0x10331a920
,命令释义:以0x10331a920这个内存地址去读取3串内存地址,每串8个字节数以16进制的格式打印;
1
2
3
4
5
6
7
8 (lldb) memory read/数量+格式+字节数 内存地址
(lldb) x/数量+格式+字节数 内存地址
数量:你想读取的个数
格式:x是16进制,f是浮点,d是10进制
字节数:
b:byte 1字节,h:half word 2字节
w:word 4字节,g:giant word 8字节
写内存
写内存:
(lldb) memory write 0x10331a928 9
释义:改0x10331a920
内存起的第28个字节为9,如下图
注意点:
sizeof( )它是个运算符,不是函数,在编译的时候就确定,编译之后就是个常量,如:
Student *stu = [[Student alloc] init];
NSLog(@"%zd",sizeof( stu )); 打印出来并不是stu占用多少空间,而是*stu这个指针占用的内存空间,结果:8;
class_getInstanceSize()
函数返回的仅仅是对象需要占用的内存大小,并非最终系统分配的内存大小。
相关问题二:对象的isa指针指向哪里?
相关问题三:OC的类信息存放在哪里?
实例对象里面只存放成员变量,方法不是放在实例对象里的,因为同一类可能实例化成多个对象,但会有相同的方法,这样就只需要创建一份方法,不必须每个实例对象各存一份,每个实例对象里面只存放自已特有的东西。