一个NSObject对象占用多少内存
我们都知道 我们平时编写的OC代码 底层实现其实是C/C++,然后编译器再把C/C++代码转换为汇编语言代码,汇编代码最终会变成机器语言。
所以OC的面向对象都是基于C/C++的数据结构来实现的。
那么我们OC中的对象 类 都是基于C/C++什么样的数据结构实现的呢?
我们的答案就是 根据C/C++中的结构体来实现的。
将OC代码转换为C/C++代码?
通过下面的操作 我们可以看到mian函数转换为C/C++代码
Last login: Thu Jul 16 16:55:54 on ttys002 farben@FarbendeMacBook-Pro ~ % cd /Users/farben/Desktop/底层/OC对象的本质/NSObject对象占用内存/NSObject对象占用内存 farben@FarbendeMacBook-Pro NSObject对象占用内存 % ls main.m farben@FarbendeMacBook-Pro NSObject对象占用内存 % clang -rewrite-objc main.m -o main.cpp
转换后的mian.m函数为
int main(int argc, const char * argv[]) { /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; NSObject *obj = ((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("alloc")), sel_registerName("init")); } return 0; }
但是这句命令 clang -rewrite-objc main.m -o main.cpp 并没有指定编译C/C++代码的平台,实际上iOS windows MacOS不同平台上被编译成的C/C++代码是不相同的。所以我们可以指定一下编译平台
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc 'oc源文件' -o '输出的cpp文件'
#import <Foundation/Foundation.h> //编译为mainarm64.cpp后 生成的结构体 //即为 NSObject_implementation 就是NSObject的底层实现 struct NSObject_IMPL { Class isa; }; //我们直接通过NSObject 点进去可以看到OC中是这样定义NSobject的 //可以看到 和C/C++的结构体实现大概差不多 可以证明我们OC中的类 底层是通过 //C/C++的结构体来实现的 /* @interface NSObject <NSObject> { Class isa; } @end */ int main(int argc, const char * argv[]) { @autoreleasepool { NSObject *obj = [[NSObject alloc] init]; } return 0; }
思考 一个OC对象在内存中是如何布局的?
NSObject的底层实现
@interface NSObject <NSObject> { Class isa; } @end
转换为C/C++后是这样的
struct NSObject_IMPL { Class isa; };
可以知道NSObject 这个对象在内存中就是一个结构体 那么isa是什么东西呢,我们点进去可以发现是一个指向结构体的指针 既然是个指针 那么在64位占有8个字节 32为4个字节
typedef struct objc_class *Class;
就是一个指向结构体的指针,意思就是指向这个类的指针。所以isa是个指针,那么在64位环境下占8个字节 在32位环境下占4个字节。
那么如果我们写了这句代码
NSObject *obj = [[NSObject alloc] init];
首先 alloc 分配存储空间给NSObject这个结构体 空间里面只有一个isa isa这个指针指向的地址 就是刚刚分配的地址 obj这个指针也是指向这个地址
但是在实际中 我们使用运行时获取一个类的大小的时候class_getInstanceSize([NSObject class])获取NSObject类实例对象的内存对齐后的成员变量所占的大小 返回8个字节 但是使用malloc_size((__bridge const void *)obj) 获取的obj所指向内存的带下 返回的是16个字节 所以声明一个类对象 系统所分配的内存空间是16个字节
一个NSObject对象占有多少内存
系统会分配16个字节给NSObject对象,但是在64位环境下NSObject只使用了8个字节。在alloc分配空间的时候 其实是调用allocWithZone 其内部 如果一个对象的成员变量小于16个字节的时候,内存会强制分配16个字节的空间。也就是说一个OC对象最小占用的内存空间是16个字节
class_getInstanceSize([NSObject class]) 返回内存对齐后的所有的成员变量所占的内存空间 (内存对齐 所占字节最大的成员变量占有内存的倍数)也就是一个成员变量最少需要的内存大小
malloc_size((__bridge const void *)obj) 创建一个实例对象 实际分配的空间 至少是16字节 也有内存对齐的概念 16的倍数
OC对象可以分为 实例对象 类对象 元类对象
实例对象:内存中存储着自己的成员变量(成员变量的赋值信息) 包括(isa指针)
类对象:一个类的类对象在内存中只有一份
类对象在内存中存放着isa指针 superClass指针 类的属性信息 类的对象方法信息 类的协议信息 类的成员变量信息(成员变量的描述信息 类型 名字等)
元类对象:获取方法 Class mataClass = object_getClass([NSObject class]);(传递的参数必须是类对象)
元类对象:存储的是类方法信息 isa指针 superClas指针 每个类有且只有一个元类对象
实例对象的isa指针指向自己的类对象(class) 类对象的isa指针指向元类对象
当调用对象方法时 通过实例对象的isa指针找到Class 最后找到其存储的对象方法进行实现
当调用类方法时 通过类对象的isa指针指向元类对象 最后找到其存储的类方法进行实现
superClass其实就是指向自己的父类 假如一个子类对象调用了父类的方法 那么流程是这样的子类对象通过isa自己的类对象 然后自己的类对象通过superClass找到父类对象 然后父类对象对象在从自己存储的对象方法中找到实现
每个类都有自己的元类对象 元类对象也包含着isa指针 superClass指针 那么元类对象怎么理解呢?
类对象的superClass指针 指向父类的类对象 元类对象的superClass指针指向父类的元类对象中
那么如果我们调用一个类方法 其实流程是这样的 先通过自己的isa指针找到自己的元类对象 然后调用元类对象保存的类方法来实现
我们可以总结一下:
实例对象的isa指针 指向自己的类对象 类对象的isa指针自己的指向元类对象 类对象的superClass指针指向自己的父类类对象 如果没有父类superClass就为空nil 元类的superClass指针指向自己的父类的元类对象
实例对象调用对象方法:通过isa找到自己的类对象 然后在类对象存储的对象方法中找到方法实现
实例对象调用父类的对象方法:通过isa指针找到自己的类对象 类对象通过自己的superClass指针找到他的父类对象 在父类对象存储的对象方法中找到方法实现
类对象调用自己的类方法: 类对象通过自己的isa指针找到自己的元类对象 然后在元类对象存储的类方法中找到方法实现
类对象调用自己父类的类方法: 通过自己的isa指针找到自己的元类对象 元类对象通过superClass指针找到父类的元类对象 然后在父类元类对象存储的类方法中找到方法实现
那么现在只有一个问题了 元类对象的isa指向哪里呢?
其实在元类对象的isa是指向基类的元类对象 在OC中我们可以认为基类是NSObject 值得注意的是基类的元类对象的superClass指针指向了基类的类对象
OC中类的构成信息探究
struct objc_class { Class _Nonnull isa OBJC_ISA_AVAILABILITY; #if !__OBJC2__ Class _Nullable super_class OBJC2_UNAVAILABLE; const char * _Nonnull name OBJC2_UNAVAILABLE; long version OBJC2_UNAVAILABLE; long info OBJC2_UNAVAILABLE; long instance_size OBJC2_UNAVAILABLE; struct objc_ivar_list * _Nullable ivars OBJC2_UNAVAILABLE; struct objc_method_list * _Nullable * _Nullable methodLists OBJC2_UNAVAILABLE; struct objc_cache * _Nonnull cache OBJC2_UNAVAILABLE; struct objc_protocol_list * _Nullable protocols OBJC2_UNAVAILABLE; #endif } OBJC2_UNAVAILABLE;
OC1.0的构成是以上信息 和我们预想的差不多 包含了isa指针 superClass指针 类名 版本 大小 成员变量 方法列表等信息 但是现在是OC2.0了 最新的信息构成如下
struct objc_class { Class isa; Class superclass; cache_t cache; //方法缓存 class_data_bits_t bits; //用户获取类的具体信息 }
bits.data()可以获取类的具体信息 struct class_rw_t
//可读写的表 class_rw_t *data() const return bits.data(); } class_rw_t* data() const { return (class_rw_t *)(bits & FAST_DATA_MASK); }
struct class_rw_t { uint32_t flags; uint32_t version; const class_ro_t *ro; method_list_t * methods; // 方法列表 property_list_t *properties; // 属性列表 const protocol_list_t * protocols; // 协议列表 Class firstSubclass; Class nextSiblingClass; char *demangledName; };
class_ro_t 是这样定义的
struct class_ro_t { uint32_t flags; uint32_t instanceStart; uint32_t instanceSize; // instance对象占用的内存空间 #ifdef __LP64__ uint32_t reserved; #endif const uint8_t * ivarLayout; const char * name; // 类名 method_list_t * baseMethodList; protocol_list_t * baseProtocols; const ivar_list_t * ivars; // 成员变量列表 const uint8_t * weakIvarLayout; property_list_t *baseProperties; };
对象的isa指针指向哪里?
实例对象的isa指针指向自己的class对象
类对象的isa指针指向元类对象
元类对象的isa指针指向基类(NSObejct)的元类对象
OC的类信息存放在哪里?
OC的类信息 包含属性信息 协议信息 方法信息 成员变量信息 父类信息
父类信息包含在类对象的superClass指针里
对象方法 属性 成员变量 协议信息存放在类对象中
类方法存放在元类对象中
成员变量的具体值存放在实例对象里面