探索OC对象的创建(上)

206 阅读3分钟

准备

LLVM调试命令

1. po:   为 print object 的缩写,显示对象的文本描述
2. bt:   打印函数的堆栈  
3. register read    读取寄存器
4. x/nuf 
    n表示要显示的内存单元的个数
    u表示一个地址单元的长度:
    取值范围: 
            b 单字节  
            h 表示双字节
            w 表示四字节
            g 表示八字节
    f表示显示方式:
    取值范围:
            x 按十六进制格式
            d 按十进制格式
            u 按十进制格式显示无符号
            o 按八进制格式
            t 按二进制格式
            a 按十六进制格式
            i 指令地址格式  
            c 按字符格式
            f 按浮点数格式

开源代码

alloc做了什么

Person *p1 = [Person alloc];
Person *p2 = [p1 init];
Person *p3 = [p1 init];
NSLog(@"p1: %@--%p--%p",p1, p1, &p1);
NSLog(@"p2: %@--%p--%p",p2, p2, &p2);
NSLog(@"p3: %@--%p--%p",p3, p3, &p3);

// -----------
p1: <Person: 0x600002f5c020>--0x600002f5c020--0x16b09bfc8
p2: <Person: 0x600002f5c020>--0x600002f5c020--0x16b09bfc0
p3: <Person: 0x600002f5c020>--0x600002f5c020--0x16b09bfb8

p1/p2/p3 三个不同的指针 都指向了同一块内存。 由上面的现象我们可以看出: alloc负责分配内存; init并未影响其内存的分配。

大胆猜测一下:init是用来提供给开发者初始化基础数据的统一方法,底层并没有实际的实现上的处理。

查看源码

最简单的探索方法就是在alloc之前打个断点,使用xcode中的 debug -> debug workflow -> Always Show Disassembly, 查看断点后的汇编窗口。

我们可以在合适的位置打断点,之后再开启alloc的符号断点,否则alloc调用过于频繁,会让你崩溃。

断点_objc_rootAllocWithZone,其内部调用了calloc, 继续后并没有运行init,而是又调用了一次_objc_rootAllocWithZone->calloc,此时calloc内部调用_malloc_zone_calloc,由此可知alloc内部其实调用了两次calloc第二次调用的时候,系统才进行对象的创建和内存的分配。

调用流程图

  • cls->instanceSize : 计算出创建对象所需的内存空间大小
  • calloc : 向系统申请开辟内存,并返回地址指针
  • obj->initInstanceIsa:关联到相对应的类

内存对齐

结构体内存对齐的规则

  1. 数据成员对⻬规则:结构(struct)的第一个数据成员放在offset为0的地方,以后每个数据成员存储的起始位置要从该成员大小或者成员的子成员大小的整数倍开始(比如int为4字节,则要从4的整数倍地址开始存储)

  2. 结构体作为成员:如果一个结构里有某些结构体成员,则结构体成员要从其内部最大元素大小的整数倍地址开始存储.(struct a里存有struct b,b里有char,int ,double等元素,那b应该从8的整数倍开始存储)

  3. 收尾工作:结构体的总大小,也就是sizeof的结果必须是其内部最大成员的整数倍,不足的要补⻬