上一篇讲解了内存对齐和calloc分析,探索要继续,这篇我们继续探索
一、isa关联类
这个部份我们就根据 alloc 新建并初始化的对象流程来分析,当alloc -> _objc_rootAlloc -> callAlloc 到 callAlloc 时,这时是才是真正的开始
根据上图看 !nonpointer 是,其中只对 cls 赋了值,然后就没有其他信息了,再看 else 的内存可以看到的是,除了 shiftcls 还有其他信息,比如bits,has_cxx_dtor,这就是 !nonpointer 判断的意义点了 。
然后我们应该看下 isa_t 这个结构体
二、isa 结构体分析
位域的声明:
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其中的一个流程,如下图
我们根据 canAllocFast 这个方法追踪下去,就到