01-OC对象的本质

探究:
1.objc_alloc流程
2.init操作
3.开辟内存

YGPerson * p1 = [YGPerson alloc];
YGPerson * p2 = [p1 init];
YGPerson * p3 = [p1 init];
/**
--0x600003d14300--0x7ffee5b5d0e8 //p1
--0x600003d14300--0x7ffee5b5d0e0 //p2
--0x600003d14300--0x7ffee5b5d0d8 //p3
*/
  1. alloc开辟内存.
  • objc_alloc流程
    1.alloc - _objc_rootAlloc- callAlloc -objc_msgSend
    2.callAlloc函数内部
    cls->ISA()->hasCustomAWZ(): 是否重写+ alloc+ allocWithZone 方法,是否执行消息转发流程。
    _objc_rootAllocWithZone_class_createInstanceFromZone
    核心函数
_class_createInstanceFromZone(Class cls, size_t extraBytes, void *zone,
                              int construct_flags = OBJECT_CONSTRUCT_NONE,
                              bool cxxConstruct = true,
                              size_t *outAllocatedSize = nil)
{

    // 几个核心函数如下
    // 计算对象的大小
    size_t size;
    size = cls->instanceSize(extraBytes); 
   //开辟内存
    id obj;
    if (zone) {
        obj = (id)malloc_zone_calloc((malloc_zone_t *)zone, 1, size);
    } else {
        obj = (id)calloc(1, size);
    }
    // 初始化cls信息 obj与cls绑定
   if (!zone && fast) {
        obj->initInstanceIsa(cls, hasCxxDtor);
    } else {
        // Use raw pointer isa on the assumption that they might be
        // doing something weird with the zone or RR.
        obj->initIsa(cls);
    }
    return obj;
}

流程图:


image.png
  1. init操作.
    init是一个工厂模式,作为析构函数,给子类重写使用,提供接口便于扩展
    new相当于alloc init操作 源码如下:
+ (id)new {
    return [callAlloc(self, false/*checkNil*/) init];
}
id
_objc_rootInit(id obj)
{
    // In practice, it will be hard to rely on this function.
    // Many classes do not properly chain -init calls.
    return obj;
}
/**
直接将self返回,实际上就是为子类提供一个工厂方法,让子类 做自定义的初始化操作
*/
  1. p1 p2 p3三个指针指向同一块内存,三个指针地址为连续的.
  • 字节对齐及其原理
    首先计算所需内存大小
inline size_t instanceSize(size_t extraBytes) const {
        if (fastpath(cache.hasFastInstanceSize(extraBytes))) {
            return cache.fastInstanceSize(extraBytes);
        }
        size_t size = alignedInstanceSize() + extraBytes;
        // CF requires all objects be at least 16 bytes.
        if (size < 16) size = 16;
        return size;
    }
uint32_t unalignedInstanceSize() const {
        ASSERT(isRealized());
        return data()->ro()->instanceSize;
    }
   // Class's ivar size rounded up to a pointer-size boundary. 
uint32_t alignedInstanceSize() const {
        return word_align(unalignedInstanceSize());
    }
// 重点
static inline uint32_t word_align(uint32_t x) {
    return (x + WORD_MASK) & ~WORD_MASK;
}
#   define WORD_MASK 7UL

举例:
[Person alloc]时,所需内存大小sizealignedInstanceSize决定
unalignedInstanceSize是8字节,来源于Person的父类NSObject中的Class isa.
由上面源码 得知 word_align算法

(8 + 7) & ~7 即  15 & ~7 
0000 1111 (15的二进制 )
0000 0111 (7的二进制)
1111 1000 (~7的二进制)

15 & ~7 与运算如下
0000 1111
1111 1000
结果
0000 1000  转换为二进制是8
8字节对齐,取8的整数,alignedInstanceSize结果为8
但是
最终计算结果 if (size < 16) size = 16可知,size小于16时,结果取16
源码上面有体现。


  • 内存对齐的原则 补充
    1.结构体(struct)或者联合体(union)的数据成员,第一个数据成员放在offset为0的地方以后每个数据成员存储的起始位置要从该成员大小或者成员的子成员大小(只要该成员有子成员,比如说是数组,结构体等)的整数倍开始(比如int为4字节,则要从4的整数倍地址开始存储)。
    2.如果一个结构里有某些结构体成员,则结构体成员要从其内部最大元素大小的整数倍地址开始存储。(struct a里存有struct b,b里有char,int,double等元素,那b应该从8的整数倍开始存储)。
    3.结构体的总大小,也就是sizeof的结果,必须是其内部最大成员的整数倍。不足的要补齐。

  • 基本数据类型的内存大小


    基本数据类型
  • 计算机基础

十六进制数字与二进制数字的对应关系如下:
0000 -> 0、0001 -> 1、0010 -> 2、0011 -> 3、
0100 -> 4、0101 -> 5、0110 -> 6、0111 -> 7、
1000 -> 8、1001 -> 9、1010 -> A、1011 -> B、
1100 -> C、1101 -> D、1110 -> E、1111 -> F。
因此,1个16进制数对应4个二进制数位,2个16进制数位对应8个二进制数位,及1个字节

计算得出内存占用大小

struct Person1 {
    char a; // [0]
    double b; // [8 16)
    int c; // [16 20)
    short d; // [20 22)  ----->24
}MyPerson1;

struct Person2 {
    long b; // [0 8)
    char a; // [8]
    int c; // [12 16)
    short d; // [16 17]  ----->24
}MyPerson2;

struct Person3 {
    long b;//[0 8)
    int c;//[8 12)
    char a;//[12]
    short d;//[14 15] -------->16
}MyPerson3;

struct Person4 {
    double a;                  //[0,7]
    int b;                         //[8,11]
    char c;                     //[12]
    short d;                   //跳过13 [14,15]
    int e;                        // [16,19]
    struct Person1 str; //根据准则2,Person1最大元素为`double`类型,所以从24开始。根据`Person1 `分配的时候24个字节,所以str为[24,47]
} Person4;


x/nuf ------->x/4gx
n 表示要显示的内存单元的个数


u 表示一个地址单元的长度
b 表示单字节
h 表示双字节
w 表示4字节
g 表示8字节


f 表示显示方式,可取以下值:
x 按十六进制格式显示变量
d 按十进制格式显示变量
u 按十进制格式显示无符号整型
o 按八进制格式显示变量
t 按二进制格式显示变量
a 按十六进制格式显示变量
i 按指令地址格式显示变量
c 按字符格式显示变量
f 按浮点数格式显示变量

你可能感兴趣的:(01-OC对象的本质)