在iOS 底层-- isa指向探究中探索了isa的指向,那么isa的结构具体是什么样的。从源码中来着手研究。
一、位域
在研究isa结构的时候,需要有位域的相关只是因为isa的机构是一个联合体+位域的形式
举个例子:
坦克大战的游戏 中 坦克的方向有上下左右的状态,
常见的写法:为其添加4个变量,
@interface JETank : NSObject
@property (nonatomic, assign) BOOL left;
@property (nonatomic, assign) BOOL right;
@property (nonatomic, assign) BOOL top;
@property (nonatomic, assign) BOOL bottom;
@end
这样可以根据每个变量去拿到相应的状态
但是这里为其分配了多少内存? ------- 8位 (BOOL为1 内存对其 结果为8)
位域的方式
因为用0|1就可以表示具体那个方向,所以我可以定义联合体(union)并且只需要一个char的长度就可以表示4个方向
@interface JETank : NSObject
{
@public
union {
uintptr_t direction;
struct {
uintptr_t left : 1;
uintptr_t right : 1;
uintptr_t top : 5; //这里定义为5 只是想说 长度可以根据不同的需求去自定义
uintptr_t bottom : 1;
};
} _jeTankDirection;
}
这样只需要对left/right 等进行相应的赋值就可以满足需求
具体赋值方法
JETank *tank = [JETank new];
/**
方法1:
tank->_jeTankDirection.direction = 0x81;
// 或者 tank->_jeTankDirection.direction = 0b0010 0001;
*/
/**
方法二:
*/
tank->_jeTankDirection.left = YES; // 为什么可以这样赋值? 因为YES强转之后为1 二进制就是0b1 是满足的
tank->_jeTankDirection.top = 31; // 这里如果赋值100会报警告(因为top占5位 最大值为 31)
tank->_jeTankDirection.bottom = 0b1; // 二进制方式赋值
NSLog(@"left = %@ top = %@ right = %@ bottom = %@",@(tank->_jeTankDirection.left),
@(tank->_jeTankDirection.top),
@(tank->_jeTankDirection.right),
@(tank->_jeTankDirection.bottom));
//打印结果
left = 1 top = 31 right = 0 bottom = 1
如果top 是赋值 100 100二进制为:0b0110 0100 因为只占5为 只取后5位 就是0b00100 最后打印结果为 top = 8
1、联合体的优势
联合体和结构体写法上有些类似,但是注意区分
联合体的所有信息公用一块内存,起到节省内存的作用
2、位域的作用
直观的表达取值范围,可以直接拿到相应的值
二、isa 结构
我们可以从源码中找到相关内容:
在底层的代码中具体为:
inline void
objc_object::initIsa(Class cls, bool nonpointer, bool hasCxxDtor)
{
assert(!isTaggedPointer());
......
}
因为传入的nonpointer 为 true 所以这里的代码可以简化为:
inline void
objc_object::initIsa(Class cls, bool nonpointer, bool hasCxxDtor)
{
isa_t newisa(0);
newisa.bits = ISA_MAGIC_VALUE; // 对bits进行初始化
newisa.has_cxx_dtor = hasCxxDtor; //赋值
newisa.shiftcls = (uintptr_t)cls >> 3; //赋值 与class 进行关联
isa = newisa;
}
在这里进行了 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_BITFIELD
在这里是宏定义(根据架构不同,内容不同),可以在进去一层查看具体定义
__x86_64__
pc端、 __arm64__
手机端64位
这里以arm64为例:
union isa_t {
isa_t() { }
isa_t(uintptr_t value) : bits(value) { }
Class cls;
uintptr_t bits;
struct {
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
};
};
对isa进行初始化的时候,对bits有一个默认的赋值,ISA_MAGIC_VALUE
为宏定义,具体为
0x001d800000000001ULL
在系统计算机上转换为二进制就是64字节
图解为:
(这张图来源网络 并稍作改动,是针对 x86_64 结构,于本文稍有出入 ( shiftcls 只到36位为止,后面依次排)
bits里面的具体信息分别代表什么意思?
bits是64为字节 struct是位域
nonpointer :1 (在bits的64位字节中 第0个用于nonpointer信息存储)
表示是否对isa指针开启指针优化;0代表纯isa指针,1代表不止是类对象指针,还包含了类信息、对象的引用计数等;
has_assoc:1 (在bits的64位字节中 第1个用于has_assoc信息存储)
关联对象标志位,0没有,1存在
has_cxx_dtor:1 (在bits的64位字节中 第2个用于has_cxx_dtor信息存储)
该对象是否有C++或者Objc的析构器,如果有析构函数,则需要做析构逻辑,如果没有,则可以更快的释放对象;
shiftcls:33 (在bits的64位字节中 第3-35用于shiftcls信息存储)
存储类指针的值。开启指针优化的情况下,在arm64架构中有33位用来存储类指针;
magic:6 (在bits的64位字节中 第36-41用于magic信息存储)
用于调试器判断当前对象是真的对象还是没有初始化的空间;
weakly_referenced :1 (在bits的64位字节中 第42个用于weakly_referenced信息存储)
标志对象是否被指向或者曾经指向一个ARC的弱变量,没有弱引用的对象可以更快释放;
deallocating :1
标志对象是否正在释放内存;
has_sidetable_rc :1
当对象引用计数大于10时,则需要借用该变量存储进位
extra_rc :19
当表示该对象的引用计数值,实际上是引用计数值减1,例如,如果对象的引用计数为10,那么extra_rc为9.如果引用计数大于10,则需要使用上面提到的has_sidetable_rc。
三、结构体(struct)与联合体(union)
union
1、可以定义多个成员,大小由最大的成员的大小决定。
2、成员共享同一块大小的内存,一次只能使用其中的一个成员。
3、对某一个成员赋值,会覆盖其他成员的值(也不奇怪,因为他们共享一块内存。但前提是成员所占字节数相同,当成员所占字节数不同时只会覆盖相应字节上的值,比如对char成员赋值就不会把整个int成员覆盖掉,因为char只占一个字节,而int占四个字节)
联合体
4、的存放顺序是所有成员都从低地址开始存放的。
- 简而言之: union的特点:共用一块内存,大小由最长的那个成员决定
对某一个成员赋值,会影响其他成员的值
结构体
本质上是多个变量集合到一起,多个变量是同时存在的,互不影响。总体的大小是各个变量值所在内存大小的和(由于内存对齐的原则,总体大小总是>=这个和值)。