OC 底层原理(3)- 对象原理(isa关联类,联合体,位域分析 )(随记)

399 阅读7分钟

上一篇讲解了内存对齐和calloc分析,探索要继续,这篇我们继续探索

一、isa关联类

这个部份我们就根据 alloc 新建并初始化的对象流程来分析,当alloc -> _objc_rootAlloc -> callAlloc 到 callAlloc 时,这时是才是真正的开始

根据断点走了下面的else,继续往下看

上图便是初始化的关键步骤,在这一步完成了创建并申请空间,还有关联类的方法,因为这部分主要分析 isa 关联类,那么接下来就是查看,关联类 isa 方法的源码 initInstanceIsa -> initIsa

红色方框标注的就是关联类赋值的地方了,然后此时可能还有个不明白的点就是什么是!nonpointer 和 nonpointer 呢?

根据上图看 !nonpointer 是,其中只对 cls 赋了值,然后就没有其他信息了,再看 else 的内存可以看到的是,除了 shiftcls 还有其他信息,比如bits,has_cxx_dtor,这就是 !nonpointer 判断的意义点了 。

然后我们应该看下 isa_t 这个结构体

二、isa 结构体分析

首先我们看到这个结构体是 union 类型,也是就是联合体.

位域的声明:

define ISA_BITFIELD

  uintptr_t nonpointer        : 1;
  uintptr_t has_assoc         : 1;
  uintptr_t has_cxx_dtor      : 1;
  uintptr_t shiftcls          : 33; /*MACH_VM_MAX_ADDRESS 0x1000000000*/ 
  uintptr_t magic             : 6;
  uintptr_t weakly_referenced : 1;
  uintptr_t deallocating      : 1;
  uintptr_t has_sidetable_rc  : 1;                              
  uintptr_t extra_rc          : 19

nonpointer :1 表示是否对isa指针开启指针优化;0代表纯isa指针,1代表不止是类对象指针,还包含了类信息、对象的引用计数等;

has_cxx_dtor:1 该对象是否有C++或者Objc的析构器,如果有析构函数,则需要做析构逻辑,如果没有,则可以更快的释放对象;

hasCxxDtor:1 该对象是否有C++或者Objc的析构器,如果有析构函数,则需要做析构逻辑,如果没有,则可以更快的释放对象;

shiftcls:33 存储类指针的值。开启指针优化的情况下,在arm64架构中有33位用来存储类指针;

magic:6 用于调试器判断当前对象是真的对象还是没有初始化的空间;

weakly_referenced :1 标志对象是否被指向或者曾经指向一个ARC的弱变量,没有弱引用的对象可以更快释放;

deallocating :1 标志对象是否正在释放内存;

has_sidetable_rc :1 当对象引用计数大于10时,则需要借用该变量存储进位

extra_rc :19 当表示该对象的引用计数值,实际上是引用计数值减1,例如,如果对象的引用计数为10,那么extra_rc为9.如果引用计数大于10,则需要使用上面提到的has_sidetable_rc。

那什么是联合体和位域呢,在接下来详解

三、联合体(union)

联合体:就是将几种数据类型联合起来的一种数据结构,但是它们共用一个空间

四、位域

位域:一般用存储一些小数据时,都会精确到字节,为了节约空间,可能就会用一个字节来存储几个变量的值,同时就会用一个结构体来标明,一个变量占用的位数方便获取,就称此为位域。

应该不太好理解,下面结合一段代码来说明

此时的内存结构是这样的 00 00 00 01 01 01 01 00 :用了4个字节来存储这些属性的值,根据属性内存对齐原则,其中4个字节还是空闲的;另外一个字节占 8 位就是2的8次方可以存储很大的数字了。所以按这种存储方法,就会造成一定的空间浪费。

然后我们换个想法,要是用char类型来表示呢?

0000 0001: 表示向前

0000 0011:表示向后

0000 0111:表示向右

0000 1111:表示向左

所以其实我们用4位就可以表示了,继续看代码理解

#define LGDirectionFrontMask    (1 << 0)
#define LGDirectionBackMask     (1 << 1)
#define LGDirectionLeftMask     (1 << 2)
#define LGDirectionRightMask    (1 << 3)

@interface TYTank(){
    // 联合体
    union {
        char bits;

        
    } _direction;
}
@end

@implementation TYTank

- (instancetype)init
{
    self = [super init];
    if (self) {
        _direction.bits = 0b0000000000;
    }
    return self;
}

/**
  0000 0000 初始值
  0000 0001 向前的 Mask值
 |                      或
  0000 0001 计算的结果 1
 
  0000 0000  初始值
  1111 1110   向前的 Mask值取反
 &
  0000 0000 计算的结果 0
 
 */

- (void)setFront:(BOOL)isFront {
    if (isFront) {
        _direction.bits |= LGDirectionFrontMask;
    } else {
        _direction.bits &= ~LGDirectionFrontMask;
    }
}

- (BOOL)isFront{
    return !!(_direction.bits & LGDirectionFrontMask);
}

以上我们就是利用联合体表示了4个属性,联合体替代属性,虽然这种写法,结果是达到了,但是在过程中并不是很好理解,而且,写法也感觉比较繁琐,这时就该我们位域出场了。通过代码分析

#define LGDirectionFrontMask    (1 << 0)
#define LGDirectionBackMask     (1 << 1)
#define LGDirectionLeftMask     (1 << 2)
#define LGDirectionRightMask    (1 << 3)

@interface TYTank(){
    // 联合体
    union {
        char bits;
        // 位域
        struct {
            char front  : 1;
            char back   : 1;
            char left   : 1;
            char right  : 1;
        };
        
    } _direction;
}
@end

@implementation TYTank

- (instancetype)init
{
    self = [super init];
    if (self) {
        _direction.bits = 0b0000000000;
        

    }
    return self;
}

/**
  0000 0000 初始值
  0000 0001 向前的 Mask值
 |                      或
  0000 0001 计算的结果 1
 
  0000 0000  初始值
  1111 1110   向前的 Mask值取反
 &
  0000 0000 计算的结果 0
 
 */

- (void)setFront:(BOOL)isFront {
    if (isFront) {
        _direction.bits |= LGDirectionFrontMask;
    } else {
        _direction.bits &= ~LGDirectionFrontMask;
    }
}

- (BOOL)isFront{
    return !!(_direction.bits & LGDirectionFrontMask);
}

- (void)setBack:(BOOL)isBack {
    _direction.back = isBack;
}

- (BOOL)isBack{
    return _direction.back;
}

- (void)setRight:(BOOL)right {
    _direction.right = right;
}

- (BOOL)right {
    return _direction.right;
}

- (void)setLeft:(BOOL)left {
    _direction.left = left;
}

- (BOOL)left {
    return _direction.left;
}

这里我就用了两种方法对bit赋值:一种是位运算,另一种是通过位域直接给位域中的属性赋值。 打印验证

然后在拓展一下位域的使用,我们还可以对位域结构,属性多样化,占的空间可以更改,如下图

struct {
            char front  : 1;
            int  back   : 8;
            char left   : 1;
            char right  : 4;
        };

以上便是我对联合体,位域的理解过程。

四、union 与 struct 的区别

1)共用体和结构体都是由多个不同的数据类型成员组成, 但在任何同一时刻, 共用体只存放一个被选中的成员, 而结构体则存放所有的成员变量。

2)对于共用体的不同成员赋值,将会对其他成员重写, 原来成员的值就不存在了, 而对于结构体的不同成员赋值是互不影响的。

3)二者的内存分配不同。联合体的大小为其内部所有变量的最大值;结构体的大小为其内部所有变量所占空间之和。

参照以上对union的理解再次回到isa的源码,分析 isa 联合体所占空间

union isa_t {
    isa_t() { }
    isa_t(uintptr_t value) : bits(value) { }

    Class cls;
    uintptr_t bits;
#if defined(ISA_BITFIELD)
    struct {
        ISA_BITFIELD;  // defined in isa.h
    };
#endif
};

联合体大小为其内部所有变量的最大值,由于 isa 这个联合体内部有结构体,结构的大小为 8 字节,uintptr_t 为 long 型,long 型也是 8 字节,由此可是 isa 这个联合体的大小为8字节。也就意味着总共有 64 位。关于 ISA_BITFIELD 结构体回看本篇第二部分

拓展:在分析isa关联类的时候提到alloc其中的一个流程,如下图

我看到断点直接走到是 else 这一步,那么为什么没直接走 if 那一步呢?我们一起来分析

我们根据 canAllocFast 这个方法追踪下去,就到

然后再继续

观察上图,有个宏定义的 #else ,说明就是有个条件的且满足才到这里的,那我们就要全局搜索看下 FAST_ALLOC 这个是怎么回事了

在这里我们找到了宏定义的地方了,仔细看上面一点这里依旧有个 #else ,然后就再往上看就有个 【#elif 1】,所以这就解释了为什么永远只会走else了。