iOS底层-OC对象
OC对象的本质
- 简介
- 我们平时编写的Objective-C代码,底层实现其实都是C\C++代码
- OC->C/C++->汇编语言->机器语言
- 所以Objective-C的面向对象都是基于C\C++的数据结构实现的
- 思考:Objective-C的对象、类主要是基于C\C++的什么数据结构实现的?
- 结构体
- 我们平时编写的Objective-C代码,底层实现其实都是C\C++代码
- 如何将OC代码转化为C++代码?
- 打开终端cd 到想要转化的文件目录,输入一下命令
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc OC源文件 -o 输出的CPP文件
- xcrun:xcode run
- -sdk iphoneos: sdk 运行在iOS平台
- clang:Xcode内置的编译器
- -arch arm64 : 编译后支持的架构
- -rewrite-objc :重写OC代码
- -o: 写入到哪个文件
- 如果需要链接其他框架,使用-framework参数。比如-framework UIKit
NSObject对象在内存中的分布
-
main.m实现如下代码
int main(int argc, const char * argv[]) { @autoreleasepool { NSObject *obj = [[NSObject alloc] init]; } return 0; }
-
command 右击进入NSObject类,定义如下:
//command右击进入NSObject类 @interface NSObject { Class isa ; } @end
-
-
将上面的main.m通过命令转化成cpp,然后搜索NSObject可以搜索到NSObject的类定义如下:
struct NSObject_IMPL { Class isa; };
- 说明以下问题:
- 说明在C/C++中NSObject类就是一个结构体,含有一个成员变量isa,那么Class呢?
-
command右击进入Class类:
typedef struct objc_class *Class;
- 发现Class就是一个结构体指针,在Arm64位中(32位中,即armv7,占4个字节),一个指针占8个字节
- 因此,推断NSObject对象(根据C++对象内存分析得出)占据8个字节
- C++对象内存分析: C++中空对象(没有成员变量)占据一个字节,非空对象占据的字节数等于成员变量占据字节数之和
NSObject *obj = [[NSObject alloc] init]; 这句话那么就是分配一个8个字节的内存,然后把地址赋值给obj变量 而且该地址也是isa的地址
-
用一些计算内存的方法验证一下上面结论是否正确:
#import <Foundation/Foundation.h> #import <objc/runtime.h> #import <malloc/malloc.h> int main(int argc, const char * argv[]) { @autoreleasepool { NSObject *obj = [[NSObject alloc] init]; // 证明依据:根据C++的结论,只要拿到成员变量的大小,就等于对象占据的内存大小 //1. 获得NSObject实例对象的成员变量所占用的大小结果为: 8 NSLog(@"%zd", class_getInstanceSize([NSObject class])); //2. 获得obj指针所指向内存的大小结果为:16 NSLog(@"%zd", malloc_size((__bridge const void *)obj)); } return 0; }
- 发现C++的结论不适应与OC对象,一个OC对象的占据的内存大小并不等于成员变量占据的大小
- 查找苹果官方源码,地址:https://opensource.apple.com/tarballs
- 在里面搜索objc,找到obc4进入下载最新的即可
-
打开源码项目,搜索class_getInstanceSize,找到实现,如下:
size_t class_getInstanceSize(Class cls) { if (!cls) return 0; return cls->alignedInstanceSize(); }
- 由此可见class_getInstanceSize方法仅仅是返回了一个类成员变量实例大小,并不是整个类的内存分配大小
-
打开源码项目,搜索allocWithZone(alloc 实际调用的方法),找到实现如下:
id _objc_rootAllocWithZone(Class cls, malloc_zone_t *zone) { id obj; #if __OBJC2__ // allocWithZone under __OBJC2__ ignores the zone parameter (void)zone; obj = class_createInstance(cls, 0); #else if (!zone) { obj = class_createInstance(cls, 0); } else { obj = class_createInstanceFromZone(cls, 0, zone); } #endif if (slowpath(!obj)) obj = callBadAllocHandler(cls); return obj; }
-
进入:
class_createInstance
id class_createInstance(Class cls, size_t extraBytes){ return _class_createInstanceFromZone(cls, extraBytes, nil); }
-
进入
_class_createInstanceFromZone
- 该方法内部调用了:
obj = (id)calloc(1, size);
- size获取:
size_t size = cls->instanceSize(extraBytes);
-
进入instanceSize内部实现:
size_t instanceSize(size_t extraBytes) { size_t size = alignedInstanceSize() + extraBytes; //CF框架规定所有的对象至少占据是16个字节 // CF requires all objects be at least 16 bytes. if (size < 16) size = 16; return size; }
- CF框架规定所有的对象至少占据是16个字节
- 该方法内部调用了:
-
- 结论:一个NSOject对象的内存大小是16个字节,但是实际上只用了8个字节
- 通过Xcode查看内存证明,NSObject对象只使用了8个字节
- 在main函数的NSLog打一个断点
- 在控制台
p obj
获取obj对象的地址为0x100517610
Debug -> Debug Workfllow -> View Memory (Shift + Command + M)
-
在Address框中输入
0x100517610
,搜索,得到结果如下41 F1 FC A6 FF FF 1D 00 00 00 00 00 00 00 00 00
- 可以看出,后8个字节全部为0
-
也可以直接通过LLDB命令获取某一段内存的值
memory read 0x100517610
- 通过Xcode查看内存证明,NSObject对象只使用了8个字节
- 常见的LLDB指令
print、p
:打印po
:打印对象- 读取内存
- memory read/数量格式字节数 内存地址
- memory read也可以简写成x :x/数量格式字节数 内存地址
- 例如: x/3xw 0x10010
- 打印3串,每一串是16进制、每一串占据4个字节
- 格式
- 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
OC对象在内存中的分布
-
main函数代码:
@interface Person : NSObject { @public int _age; } @end @implementation Person @end int main(int argc, const char * argv[]) { @autoreleasepool { Person *per = [[Person alloc] init]; per->_age = 5; // 获得Person实例对象的成员变量所占用的大小 --- 16 NSLog(@"===%zd", class_getInstanceSize([Person class])); // 获得per指针所指向内存的大小 --- 16 NSLog(@"%zd", malloc_size((__bridge const void *)per)); } return 0; }
-
编译成CPP,然后搜索Person_IMPL,如下:
//Person内部实现 struct Person_IMPL { //继承:NSObject的类 struct NSObject_IMPL NSObject_IVARS; int _age; };
-
NSObject_IMPL从上面我们知道,他是一个拥有一个成员变量isa的结构体,因此等价于下面
struct Person_IMPL { //继承:NSObject的类 Class isa; int _age; };
-
- 分析;
- 从上面的打印可以看出,Person对象占据16个字节
-
通过内存查看如下:
99 12 00 00 01 80 1D 00 05 00 00 00 00 00 00 00
- 因此可以看出,内存分配16个字节,前8个字节放isa
- 接下来4个字节放_age
- 那如果在创建一个Student继承自Person呢? (Student新增一个_no成员变量)
- 经过同样的分析,Student内存也是占据16个字节
- 前8个字节存放isa,接下来4个字节存放_age,最后4个存放_no
内存分配对齐
-
main.m中代码
@interface Student : NSObject { @public int _no; int _age; int _height; } @end @implementation Student @end int main(int argc, const char * argv[]) { @autoreleasepool { Student *stu = [[Student alloc] init]; stu->_no = 4; stu->_age = 5; // 获得Student实例对象的成员变量所占用的大小 --- 24 NSLog(@"===%zd", class_getInstanceSize([Student class])); // 获得stu指针所指向内存的大小 --- 32 NSLog(@"%zd", malloc_size((__bridge const void *)stu)); //分配4个字节的内存 void *p = malloc(4); //获得分配内存的大小--- 16 iOS平台内存分配对齐 NSLog(@"%zd", malloc_size(p)) } return 0; }
-
分析:
- 从前面的理论分析,Student有3个成员变量,加上一个父类的isa变量
- 按照结构体的内存对齐(结构体分配内存:是占据内存最大的那个成员变量的整数倍)那么,这个Student对象的内存应该是24,但是为何malloc_size打印的事32呢?
- 内存分配对齐
- iOS平台的对象分配内存时是16的整数倍。
- 按照16个字节对齐。
相关面试题
- 一个NSObject对象占用多少内存?
- 系统分配了16个字节给NSObject对象(通过malloc_size函数获得)
- 但NSObject对象内部只使用了8个字节的空间(64bit环境下,可以通过class_getInstanceSize函数获得)
OC对象的种类
- Objective-C中的对象,简称OC对象,主要可以分为3种
- instance对象(实例对象)
- class对象(类对象)
- meta-class对象(元类对象)
instance对象
- instance对象就是通过类alloc出来的对象,每次调用alloc都会产生新的instance对象
-
举例:
NSObject *object1 = [[NSObject alloc] init]; NSObject *object2 = [[NSObject alloc] init];
- object1、object2是NSObject的instance对象(实例对象)
- 它们是不同的两个对象,分别占据着两块不同的内存
- instance对象在内存中存储的信息包括
- isa指针
- 其他成员变量
class 对象
-
举例:
NSObject *object1 = [[NSObject alloc] init]; NSObject *object2 = [[NSObject alloc] init]; Class objectClass1 = [object1 class]; Class objectClass2 = [object2 class]; Class objectClass3 = [NSObject class]; Class objectClass4 = object_getClass(object1); Class objectClass5 = object_getClass(object2); //一个类的类对象在内存中只有一份,打印地址都一样 NSLog(@"%p %p %p %p %p ",objectClass1,objectClass2,objectClass3,objectClass4,objectClass5);
- objectClass1 ~ objectClass5都是NSObject的class对象(类对象)
- 它们是同一个对象。每个类在内存中有且只有一个class对象
-
实例对象在内存中存储的是对应实例的成员变量,那么类对象呢?
- 我们可以分析,类对象是存储实例对象都共有的东西
- class对象在内存中存储的信息主要包括
- isa指针
- superclass指针
- 类的属性信息(@property)、类的对象方法信息(instance method)
- 类的协议信息(protocol)、类的成员变量信息(ivar)
- …
mata-class 对象
-
举例
Class objectMetaClass = object_getClass([NSObject class]);
- objectMetaClass是NSObject的meta-class对象(元类对象)
- 每个类在内存中有且只有一个meta-class对象
- meta-class对象和class对象的内存结构是一样的,但是用途不一样,在内存中存储的信息主要包括
- isa指针
- superclass指针
- 类的类方法信息(class method)
- …
- 注意:
-
以下代码获取的objectClass是class对象,并不是meta-class对象
Class objectClass = [[NSObject class]class]; //查看Class是否为meta-class BOOL result = class_isMetaClass([NSObject class]);
-
object_getClass与objc_getClass函数的区别:
Class object_getClass(id obj); 1. 如果参数为实例对象,返回的是类对象 2. 如果参数为类对象,返回的是元类对象 3. 如果参数为元类对象,返回的是根类对象 4. 本质就是返回参数对象的isa指针 Class objc_getClass(const char * _Nonnull name); 1. 根据一个字符串返回一个类对象
-