OC 对象本质
2020-06-10 本文已影响0人
xiaoyouPrince
首先从 NSObject 的头文件可以看到 NSObject 的对象定义,其成员变量只有一个 isa 指针,指向自己所属的 Class。
@interface NSObject {
Class isa;
}
@end
使用 Clang 编译器,重写 OC 文件为 C++ 语言。也可以指定 xcrun 的目标系统。
// clang 直接重写,会生成全量的cpp文件,文件跨平台,单数据量较大
clang -rewrite-objc main.m -o main.cpp
// 指定运行环境和架构,生成资源会小
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m -o main-arm64.cpp
打开 main-arm64.cpp 文件,可以看到内部对于 NSObject 的实现就是一个 C/C++ 结构体,其成员只有一个指向自己类的 isa 指针。
struct NSObject_IMPL {
Class isa;
};
// Class 是一个 objc_class 的结构体指针
typedef struct objc_class *Class;
// objc_class 再往下走是 runtime 内部的定义,其成员也只有一个 isa 指针
struct objc_class {
Class isa;
}
从下面代码的思考赋值过程,想一下 obj 对象占用多少空间?
NSObject *obj = [[NSObject alloc] init];
右侧创建一个 NSObject 对象,并分配堆内存空间,其成员只有一个 isa 指针(8字节)
左侧创建一个 obj 指针,将右侧的堆空间的地址值赋值给 obj 指针。
NSObject 子类的本质
写一个 Person 类,只有一个成员,通过 clang 重写为 C++ 代码。可以发现
// OC 类定义
@interface Person : NSObject
{
char *name;
}
@end
// C++ 结构体重写
struct Person_IMPL {
struct NSObject_IMPL NSObject_IVARS;
char *name;
};
即本质上,NSObject 类继承的子类在实现的时候会将父类的成员变量都复制一份放到自己的成员列表中
通过查看底层的数据结构,能计算出 Obj 对象和 person 对象分别占用 8、16 个字节,此处可以通过 runtime 的 class_getInstanceSize
方法得到验证。也可以通过将对象的指针转换成底层结构体的数据结构来验证其底层实现。
struct Person_IMPL {
Class isa; // struct NSObject_IMPL NSObject_IVARS;
char *name;
};
int main(int argc, const char * argv[]) {
@autoreleasepool {
NSObject *obj = [[NSObject alloc] init];
Person *per = [[Person alloc] init];
per->name = "hello";
// 使用底层结构体指针指向创建的 per 对象,可以证明其底层的结构是一样的
struct Person_IMPL *per_imp = (__bridge struct Person_IMPL *)per;
per_imp->name = "world";
NSLog(@"per.name = %s",per->name); // per.name = world
NSLog(@"%zd\n%zd",class_getInstanceSize(obj.class),class_getInstanceSize(per.class)); // 8 16
// 这里涉及到字节对齐,因为 isa 是8个字节,最终肯定是8的倍数,如果在加一个 int age;变量其最终大小为 24
}
return EXIT_SUCCESS;
}
更复杂的子类继承关系
实际使用中继承关系可能会有很多层,如:NSObject -> Person -> Father -> Son。 实际上无论继承多少层,其对象内部的内存布局都是一样的,如下
struct NSObject_IMPL {
Class isa; // 8
};
struct Person_IMPL {
struct NSObject_IMPL NSObject_IVARS; // 8
char *name; // 8
int age; // 4 一共24字节,内存对齐,空置最后4字节
};
struct Father_IMPL {
struct Person_IMPL Person_IVARS; // 24
int height; // 4 一共24字节,内存对齐,补前面空的4字节
};
struct Son_IMPL {
struct Father_IMPL Father_IVARS; // 24
char tag; // 1 一共48字节,内存对齐,空置后23字节
};
因为在底层实现中,所有类都是结构体,结构体本身内存需要根据其成员进行内存对齐,所以各类对象所占据的内存如上注释:
还可以通过查看对象的内存和LLDB 的方式查看其内存数据。
面试题
Q: 一个 NSObject 对象占用多少内存?
A: 经过查看 NSObject 对象的内存结构发现:一个 NSObject 对象在内存中只有一个 isa 指针,所以其占用内存大小为【64bit 占8字节,32bit 占4个字节】