前言
通过上一篇文章《iOS对象的本质》可以知道对象在底层被编译成结构体,其中第一个属性就是对象的isa
。那么什么是isa
呢?isa
的包含哪些信息呢?isa
的结构怎么的呢?它跟对象又是怎么联系在一起的?带着种种的问题我们往下走吧!(#.#)
准备工作
联合体(union)
在开发过程中常用到一种结构体类型 struct
,有一种和结构体
比较相似的结构,叫共用体
,也称联合体
。代码如下:
// 联合体 : 互斥
union myPersion {
short money; //2
int age; //4
double height ; //8
};
int main(int argc, const char * argv[]) {
@autoreleasepool {
union myPersion persion;
persion.money = 1;
NSLog(@"money=%d---age=%d---height=%f",persion.money,persion.age,persion.height);
persion.age = 18;
NSLog(@"money=%d---age=%d---height=%f",persion.money,persion.age,persion.height);
persion.height = 170.0;
NSLog(@"money=%d---age=%d---height=%f",persion.money,persion.age,persion.height);
NSLog(@"persion联合体的大小=%ld",sizeof(persion));
}
运行结果:
2021-06-16 15:39:03.030731+0800 001-联合体位域[66996:1423574] money=1---age=1---height=0.000000
2021-06-16 15:39:50.484786+0800 001-联合体位域[66996:1423574] money=18---age=18---height=0.000000
2021-06-16 15:40:14.492142+0800 001-联合体位域[66996:1423574] money=0---age=0---height=170.000000
2021-06-16 15:40:54.421131+0800 001-联合体位域[66996:1423574] persion联合体的大小=8
得出结论:
- 联合体可以定义多个不同类型的成员,联合体的内存大小由其中
最大的成员的大小决定
。 - 联合体中
修改
其中的某个变量会覆盖其他变量的值或者取代其他的值
。(union内存块大小够之前变量存放的时候会覆盖,不够就取代)
- 联合体所有的变量
公用一块内存
,变量之间互斥
。
与结构体(struct)相比;联合体(union)的优缺点: 结构体(struct)中所有变量是“共存”的——优点是“有容乃大”, 全面;缺点是struct内存空间的分配是粗放的,不管用不用,全分配。
联合体(union)中是各变量是“互斥”的——缺点就是不够“包容”; 但优点是内存使用更为精细灵活,也节省了内存空间.
位域(Bit field)
在开发过程中为了更加的合理利用内存
,有些信息在存储时,并不需要占用一个完整的字节,而只需占几个或一个二进制位。例如BOOL
值在存放只有一个只有0
和1
两种状态成员, 用一位二进位即可。分析代码如下:
// 4 位
// 1 字节 3倍浪费
struct LGCar1 {
BOOL front; // 0 1
BOOL back;
BOOL left;
BOOL right;
};
// 位域
// 互斥
struct LGCar2 {
BOOL front: 1;
BOOL back : 1;
BOOL left : 1;
BOOL right: 1;
};
int main(int argc, char * argv[]) {
@autoreleasepool {
struct OldCar oldCar;
struct NewCar newCar;
NSLog(@"----%lu----%lu",sizeof(oldCar),sizeof(newCar));
}
return 0;
}
运行结果:
2021-06-16 16:00:20.379723+0800 001-联合体位域[67528:1433973] ----4----1
分析结果:
好明显oldCar
结构体的大小为4
字节,newCard
经过位运算大小为1
字节。在newCard
结构体1个字节的内存中,front
、back
、left
和right
都是占用1
位,表示形式为:0000 1111。
isa与类之间的关联流程
在《alloc底层原理探索》一文中已经结合alloc源码详细分析了一波isa指针与类是怎么样关联的。这里我就粗略的分析一下。
alloc
一个对象最核心的三个方法cls->instanceSize
计算内存大小 ,(id)calloc(1, size)
开辟内存返回地址指针,obj->initInstanceIsa
初始化isa关联类。具体的流程总结为:alloc
--> _objc_rootAlloc
--> callAlloc
--> _objc_rootAllocWithZone
-->_class_createInstanceFromZone
,断点在 obj->initInstanceIsa
。
bj->initInstanceIsa源码流程分析:
initInstanceIsa方法
initIsa方法
isa_t
分析结果:
isa_t
其实就是一个联合体(union)
,sa_t
有两个变量 一个是bits
,一个是cls
。通过上面分析联合体是互斥的,那就意味着初始化isa有两种方式:
bits被赋值,cls 没有值或者被覆盖
-
cls 被赋值,bits没有值或者被覆盖
那么互斥一定存在一个变量嘛?变量都会被覆盖或者没有值嘛?联合体有没有具体的操作打破这种规律?哈哈哈,见下图:
我们尊敬的kc老师想都没想就给出他非常专业的答案:这个union进行了description表达
isa的结构
首先我们进去ISA_BITFIELD
字段,看到的定义如下图:
x86isa结构
isa各个字段在其64位的分布
isa内部结构分布
各变量的含义
-
nonpointer
:表示是否对isa指针进行优化,0
表示纯指针,1
表示不止是类对象的地址,isa中包含了类信息、对象、引用计数等 -
has_assoc:
关联对象标志位,0
表示未关联,1
表示关联 -
has_cxx_dtor
:该对象是否C ++
或者Objc
的析构器,如果有析构函数,则需要做析构逻辑,没有,则释放对象 -
shiftcls
:储存类指针的值,开启指针优化的情况下,在arm64架构
中有33
位用来存储类指针,x86_64
架构中占44
位 -
magic
:用于调试器判断当前对象是真的对象还是没有初始化的空间
-weakly_referenced
:指对象是否被指向或者曾经指向一个ARC
的弱变量,没有弱引用的对象可以更快释放 -
deallocating
:标志对象是否正在释放 -
has_sidetable_rc
:当对象引用计数大于10
时,则需要借用该变量存储进位 -
hextra_rc
:表示该对象的引用计数值,实际上引用计数值减1,例如,如果对象的引用计数为10
,那么extra_rc为9
,如果大于10
,就需要用到上面的has_sidetable_rc
isa结构分析的总结: - 首先isa指针分为纯isa指针与非纯isa之整,
nonpointer
进行标记区分。 -
isa
是联合体+位域的方式存储信息的。采用这种方式的有点就是节省大量内存。发挥了联合体的优势节省了比较多的内存空间。
initIsa深入分析
首先定义一个XXPersion
,当newisa(0)
调用完时候发现bits=0
、cls=nil
且其他成员信息都为0
,如下图所示:
纯isa指针
setClass逻辑
验证位运算类信息地址
分析:
-
shiftcls = 537307166
与上面的算法shiftcls=(uintptr_t)newCls >> 3
得到的结果是一样的。 -
XXPerson
的类地址>>3进行10
进制转换赋值给shiftcls
。此时isa已经关联XXPerson类 ,cls变量被覆盖 ,cls = XXPerson
。
补充:进行右移3位的目的主要是保持8字节的对齐,也就是说指针的地址只能是8的倍数,那么指针地址的后3位只能是0。同时虚拟内存中arm64架构的中间33位是类的地址,加上后面补0的三位,类的地址为36位,同理x86架构下的类地址为44+3=47
isa位运算
位运算操作位运算过程
p/x 0x011d800100004561 >> 3
(long) $38 = 0x0023b000200008ac
(lldb) p/x 0x0023b000200008ac << 20
(long) $39 = 0x000200008ac00000
(lldb) p/x 0x000200008ac00000 >> 17
(long) $40 = 0x0000000100004560
位运算过程图像分析
isa与ISA_MASK(掩码)进行&运算
掩码解析类地址分析总结:
p/x 0x011d800100004561 & 0x00007ffffffffff8ULL
的结果是XXPersion
,说明了isa已经关联了类。由图可知掩码(ISA_MASK)的二进制也是保留了中间的44位,其他位置为0
,跟类在isa中的位置关系(shiftcls)
是对应的。
网友评论