一、本质
OC对象的本质是C和C++中的
结构体
-
OC代码转C++代码:
clang -rewrite-objc main.m -o main.cpp
- 转化成什么平台的代码,不同平台支持的代码不一样。例如:Windows、MacOS,iOS。
- iOS:模拟器(i386/x86_64)、32bit(armv7)、64bit(arm64)
- 命令:
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m -o main_arm64.cpp
Class
是一个指向结构体
的指针。
typedef struct objc_class *Class
指针在64位机器中占8个字节
sizeof()
:是针对类型的,得到一个类型占多少内存。sizeof不是函数,是运算符,编译过后就变成了常数,不存在函数调用(跟宏定义有点类似)。如:sizeof(int)、sizeof(double)、sizeof(struct Student_IMPL)
NSObject *obj = [[NSObject alloc] init];
// 获取NSObject类的实例对象成员变量所占用的大小 >> 8,返回结构体内存对齐过后的成员变量大小
NSLog(@"%zd", class_getInstanceSize([NSObject class]));
// 获取obj所指向内存的大小 >> 16
NSLog(@"%zd", malloc_size((__bridge const void *)obj));
sizeof(struct Student_IMPL); // 结构体Student_IMPL的大小
sizeof(obj) --> 8 // obj是个指针,占8个字节
sizeof(int) --> 4 // int占4个字节
int a = 10;
sizeof(a) --> 4
// May be unaligned depending on class's ivars.
uint32_t unalignedInstanceSize() {
assert(isRealized());
return data()->ro->instanceSize;
}
// Class's ivar size rounded up to a pointer-size boundary.
uint32_t alignedInstanceSize() {
// 返回内存对齐过后的大小(align对齐)
return word_align(unalignedInstanceSize());
}
// class_getInstanceSize的实现
size_t class_getInstanceSize(Class cls)
{
if (!cls) return 0;
return cls->alignedInstanceSize();
}
A、题:一个NSObject对象占用多少内存?
答:1、系统分配了16个字节给NSObject对象(通过malloc_size函数获得) 2、 但NSObject对象内部只使用了8个字节的空间(64位环境下,通过class_getInstanceSize函数获得)
注意:Class isa
只占8个字节,但是[NSObject alloc]会分配16个字节。OC对象分配的内存最少16个字节
// calloc函数分配内存大小的时候调用的size函数,所有OC对象最少会分配16个字节
size_t instanceSize(size_t extraBytes) {
size_t size = alignedInstanceSize() + extraBytes;
// CF requires all objects be at least 16 bytes.
if (size < 16) size = 16;
return size;
}
struct Student_IMPL {
// struct NSObject_IMPL NSObject_IVARS;
Class isa; // 8字节
int _no; // 4字节
int _age; // 4字节
};
s->_no = 4;
通过指针直接访问成员变量
Student:NSObject
占16个字节,8 + 4 + 4
结构体内存对齐:
结构体的最终大小必须是最大成员大小的倍数。
struct NSObject_IMPL {
Class isa;
};
struct Person_IMPL {
// struct NSObject_IMPL NSObject_IVARS;
Class isa; // 8个字节
int _age; // 4个字节
}; // 16个字节 内存对齐:结构体的最终大小必须是最大成员大小的倍数
struct Student_IMPL {
// struct Person_IMPL Person_IVARS;
Class isa; // 16个字节
int _no; // 4个字节
}; // 16个字节 Class isa 里面实际用到的只有12个字节,下面的4个字节直接使用空地址
二、属性
对于成员变量的属性,只会在该类的结构体中申明对应的成员变量,不会有对应的get,set方法。
方法是不会放在实例对象里面的,因为实例对象可以创建很多份,一一不同,但是方法只需要保存一份即可。方法保存在类对象的方法列表里面
实例方法保存在类对象的方法列表里面,类方法保存在元类对象的方法列表里面(一个类的类对象和元类对象有且只有一份)。
B、题:创建一个实例对象,至少需要多少内存?
答:
1、#import
2、class_getInstanceSize([NSObject class]);
3、sizeof(struct NSObject_IMPL);
故:至少需要8个字节
C、题:创建一个实例对象,实际分配多少内存?
答:
1、#import
2、malloc_size((__bridge const void *)obj);
故:实际分配了16个字节,分配的字节数是16的倍数
// calloc分配内存的时候遵守的内存对齐
#define NANO_MAX_SIZE 256 /** Buckets sized {16, 32, 48, 64, 80, 96, 112, ...}*/
我们可以总结内存对齐为两个原则:
原则 1. 前面的地址必须是后面的地址正数倍,不是就补齐。
原则 2. 整个Struct的地址必须是最大字节的整数倍。
底层实现:
// NSObject:
struct NSObject_IMPL {
Class isa;
};
// Class
struct objc_class : objc_object {
// Class ISA;
Class superclass;
cache_t cache; // formerly cache pointer and vtable
class_data_bits_t bits; // class_rw_t * plus custom rr/alloc flags
class_rw_t *data() {
return bits.data();
}
void setData(class_rw_t *newData) {
bits.setData(newData);
}
...
}
struct objc_object {
private:
isa_t isa;
public:
// ISA() assumes this is NOT a tagged pointer object
Class ISA();
// getIsa() allows this to be a tagged pointer object
Class getIsa();
...
}
struct class_rw_t {
// Be warned that Symbolication knows the layout of this structure.
uint32_t flags;
uint32_t version;
const class_ro_t *ro;
method_array_t methods;
property_array_t properties;
protocol_array_t protocols;
Class firstSubclass;
Class nextSiblingClass;
...
}
struct class_ro_t {
uint32_t flags;
uint32_t instanceStart;
uint32_t instanceSize;
#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;
method_list_t *baseMethods() const {
return baseMethodList;
}
};