class_ro_t 中的 instanceSize
编译器把某个类的 OC 代码转为 C 代码时,子类结构体第一个变量,是父类结构体:
struct Child {
struct Parent parent_ivars;
int child_ivar;
};
所以在内存布局上相当于把父类变量及当前类变量都放在一起,然后按照 C 结构体大小规则计算:
1 结构体每个成员相对于起始地址的偏移能够被其自身大小整除,如果不能则在前一个成员后面补充字节
2 结构体总体大小能够被最宽的成员的大小整除,如不能则在后面补充字节
实际在计算 class_ro_t 中的 instanceSize 时,并没有遵守规则2,即最后不用补齐。
计算例子 (加号左边代表父类成员大小,最终父类是 NSObject,只有一个长度为8 的 isa)
8+1 = 9
8+1+4+1 = 8+4+4+1=17
8+1+1+8+1 = 8+ 8 +8+1 = 25
怎么知道没有遵守规则2 呢,这个数据是怎么来的呢,我用很笨的方法测试了一下:
id cls = MyClass.class; // 随便的类
void *p1 = (__bridge void *)cls;
uintptr_t * p2 =(uintptr_t *) p1;
uintptr_t * p3 = p2 + 4; // objc_class::bits 的地址
uintptr_t * p4 = (uintptr_t *)(*p3 & 0x00007ffffffffff8UL);
// p4 is class_rw_t*
uintptr_t *p5 = (uintptr_t *)(*(p4 + 1));
// p5 is class_ro_t*
uint32_t instanceSize = *((uint32_t *)p5 + 2);
// 这样就可以直接打印出 instanceSize 了
// 通过对 MyClass 变量设计,可以验证 instanceSize 规则
unalignedInstanceSize
即 ro->instanceSize。
alignedInstanceSize
对 unalignedInstanceSize 实施规则2,即得到。
例如 9 变 16,17 变 24,25 变 32,目前总是 8 的倍数:因为必有 isa 是 8 字节,要补齐。
class_getInstanceSize
即 alignedInstanceSize。
class_createInstance
size_t instanceSize(size_t extraBytes);
用于为实例分配空间,不是实例大小,原注释是
// CF requires all objects be at least 16 bytes.
即使 alignedInstanceSize 是 8,也要分配 16 字节空间。
为什么要求至少 16 字节呢,我觉得可能是归因于操作系统堆内存的实现 :
操作系统设计堆内存时,arena 最小规格是 一个内存块 16 字节。
事实也是这样,malloc 族系统调用,即使参数只写 1 字节,也会申请 16 字节内存,提供的规格假设依次是 16,32,64,128…,申请 65 字节内存就会实际申请 128 字节空间。
因此 runtime 为对象申请内存时,干脆就设置必须申请 16 字节及以上。。。