前语
前面在 iOS 目标底层原理之alloc剖析 中介绍了创立一个目标要害三步:
- 核算类占用的内存巨细
- 依据核算出来的类占用的内存巨细
size
,关于size
进行16进制对齐,并在堆中拓荒空间 - 类(
isa
)和创立出来的目标进行相关
前面两节已经介绍了核算类的内存巨细和拓荒内存空间
,今日来剖析一下isa 以及isa怎样和创立出来的目标进行绑定的
结构体和联合体
在探究isa
之前,先来了解一下共同体和位域,由于isa
就是经过共同体 + 位域
完成的。
- 结构体 开发中结构体算是比较常用的了,运用如下:
struct DXJPersonStatus {
BOOL sleep;
BOOL eat;
BOOL play;
BOOL study;
}pStatus; // 人的状况
NSLog(@"sizeof(pStatus) = %lu",sizeof(pStatus));
终究打印: sizeof(pStatus) = 4
,由于BOOL 占1个字节
,4个变量,所以占用4个字节,那就是 4 * 8 = 32位。这儿其实存在两个问题,第一个:BOOL
用 0 或者 1 就可以表达,但是这儿用4个字节(32位)表明;第二个:一个人不能又睡又吃又玩又学习的,睡 吃 玩 学习 只能有一种状况,从这个两个层面上考虑,这个规划是浪费了内存空间的。处理第一个问题,咱们运用:结构体 + 位域
处理。处理第二个问题,咱们运用联合体 + 位域
- 结构体 + 位域
struct DXJPersonStatus {
BOOL sleep : 1; // 1表明sleep只占用1位,eat play study同理
BOOL eat : 1;
BOOL play : 1;
BOOL study : 1;
}pStatus; // 人的状况
NSLog(@"sizeof(pStatus) = %lu",sizeof(pStatus));
终究打印: sizeof(pStatus) = 1
, 由于sleep eat play study
分别只占用1位,一共4位,所以只占用了1个字节。相比上面的单纯的结构体节省了3倍的内存空间。
空间上的确节省了,但是睡 吃 玩 学习
依旧是同时存在的,不符合常理,如下图所示,这儿运用:共同体(联合体) + 位域
- 共同体 + 位域,这儿丰富了一下内部的数据类型,便于探究不同的数据类型在结构体和联合体中的不一致,其间
DXJPersonStruct
和DXJPersonUnion
内部数据是一模一样,关于数据的赋值也是一模一样,接下来看下他们的表现。
struct DXJPersonStruct {
char *name;
int age;
double height ;
};
union DXJPersonUnion {
char *name;
int age;
double height ;
};
剖析上图:
-
sizeof(pStruct)
,其间pStruct
是一个结构体,char *
占8个字节,int
占4个字节,double
占8个字节,一共占用了[0-7],[8-11],(12-15)[16-23] => 24字节
-
sizeof(pUnion)
,其间pUnion
是一个联合体,联合体的巨细取决于体内最大成员变量的巨细,所以是8字节 -
红框打印中发现,结构体
pStruct
内部数据都正常的显示了出来:name = "DXJ", age = 18, height = 163
-
联合体:
4.1 标签1:
pUnion
只是创立,没有赋值时,内部数据都是指向的脏内存
4.2 标签2:
pUnion
,给name
赋值,age和height
依旧指向脏内存
4.3 标签3:
pUnion
,给age
赋值,发现name = ""
,height
依旧指向了脏内存
4.4 标签4:
pUnion
,给height
赋值,发现name = "" age = 0
在探究过程中咱们发现,联合体给内部成员变量赋值时,只保留当时成员变量的赋值,之前关于其他成员变量的赋值都置为了默认值,这儿得出了联合体和结构体的特点:
联合体: 各个成员变量之间是互斥
的,那缺陷就是不够容纳
,长处是内存运用更为精细灵敏,也节省了内存空间。
结构体: 各个成员变量之间是共存
的,缺陷就是无论变量是否运用都会拓荒内存空间,长处是各个成员变量之间是共存。
isa
isa
是做什么?在哪里有表现呢? 那首先查看下p
这个目标在内存中分布:
目标的isa
就是打印目标时,放在首位位置的内存地址,在libobjc.A.dylib
库objc-private
中找到了isa_t
源码(截取首要部分):
union isa_t {
// 联合体的两种析构方法
isa_t() { }
isa_t(uintptr_t value) : bits(value) { }
/**
由于isa_t是联合体,所以bits 和 cls 以及 下方的结构体 是互斥的
cls 是private类型的,所以外部只能操作bits和下方的结构体
*/
uintptr_t bits;
private:
Class cls;
public:
// __x86_64__ 装备
// 改结构体内部是共存的
struct {
uintptr_t nonpointer : 1;
uintptr_t has_assoc : 1;
uintptr_t has_cxx_dtor : 1;
uintptr_t shiftcls : 44; /*MACH_VM_MAX_ADDRESS 0x7fffffe00000*/
uintptr_t magic : 6;
uintptr_t weakly_referenced : 1;
uintptr_t unused : 1;
uintptr_t has_sidetable_rc : 1;
uintptr_t extra_rc : 8
};
// obj 和 cls 绑定
void setClass(Class cls, objc_object *obj);
// 获取类
Class getClass(bool authenticated);
// 获取编码后的类
Class getDecodedClass(bool authenticated);
};
剖析一下上方代码,首要考虑成员变量:由于isa_t
是一个联合体,所以成员变量bits 和 cls 以及结构体(结构体内部是共存的)
他们三个是互斥的,也就是
-
bits
被赋值时,cls
假如曾经有赋值则被bits
的值掩盖,假如未曾赋值则为空 -
cls
被赋值时,bits
假如曾经有赋值则被cls
的值掩盖,假如未曾赋值则为空
剖析结构体内部数据之前,先看下这些成员变量表达的意义,这是结构体+位域组成的,占8个字节 1 + 1 + 1 + 44 + 6 + 1 + 1+ 1 + 8 = 64 => 8字节
,这儿也表现了位域使内存愈加精细灵敏。以x86_64(模拟器)
做剖析:
-
nonpointer : 1
: 占用1bit,表明是否对isa
做了指针优化,1:不止类的地址,还包括了其他的信息,例如 目标的引证计数,是否有相关目标,析构函数,是否指向ARC的弱引证变量….. -
has_assoc : 1
:占用1bit,相关目标标志位,1:有,0:没有 -
has_cxx_dtor : 1
:占用1bit,该目标是否有C++或Objc的析构函数,1:有,0:没有 -
shiftcls : 44
:占用44bit,类的指针的值 -
magic : 6
:占用6bit,用于调试器判断当时目标是真的目标仍是么有初始化的空间 -
weakly_referenced : 1
:占用1bit,目标是否被指向或曾经指向一个ARC的弱变量,没有弱引证的目标可以更快开释 -
extra_rc : 8
:占用8bit,该目标的引证计数值,实际上是引证计数值减1 - …
isa底层完成总结
- isa有两种类型
nonpointer
和 非nonpointer
类型,nonpointer
包括的类其他信息,是否有相关目标,是否有析构函数,目标引证计数等;非nonpointer
是一个纯类的指针 - 在开发中,会频繁的创立目标,每个目标有包括
isa
指针,这样会占用很多的内存,所以isa
采用了联合体
+位域
的算法来完成,联合体
中成员变量互斥的特性,节省了部分内存空间,位域
的运用,优化了内存的存储,使得内存的运用愈加灵敏。这样isa
占用的内存空间得到了很大的优化。
obj绑定isa
剖析:
-
isa_t newisa(0)
创立一个isa_t
类型的实例,内部数据为空,如下图
-
假如对错
nonpointer
,也就是没有敞开指针优化,是一个纯的isa,直接将cls
和this
绑定newisa(cls, this)
(这儿的this
就是调用者,也就是obj
) -
假如是
nonpointer
类型,说明敞开了指针优化,包括了其他的信息3.1
newisa.bits = ISA_MAGIC_VALUE
,此刻除了bits
会赋值,结构体内部的magic 和 no npointer
也会赋值,magic
是占用了[48 - 53]
位,nonpointer
占用了第1位
,依据ISA_MAGIC_VALUE
掩码,核算得出magic = 59, nonpointer = 1
3.2
ISA_HAS_CXX_DTOR_BIT
在x86_64
下为1
,newisa.has_cx x_dtor = hasCxxDtor;
是否有C++
析构函数3.3
newisa.setClass(cls, this);
然后再将cls
和this
绑定(这儿的this
就是调用者,也就是obj
)
扩展 ISA_MASK
在isa_t
中发现一个获取类内存地址的方法Class getClass(bool authenticated);
其间有一个要害代码
uintptr_t clsbits = bits;
clsbits = clsbits & ISA_MASK;
// 也就是 bits = bits & ISA_MASK
ISA_MASK
:isa的掩码
,isa
分别两种,其间nonpointer
类型会包括一些其他的信息,有时候咱们指向获取到shiftcls
的信息,这儿提供了两种方法_x86_64举例
0x011d800104179e6d
,这是一个nonpointer
类型的isa
,怎样获取到shiftcls
?
- 经过位移的方法获取
// 0. x86_64的isa数据分布:这儿要获取的就是shiftcls
(magic + weakly_referenced + unused + has_sidetable_rc + extra_rc)+ shiftcls + (nonpointer + has_assoc + has_cxx_dtor) = 17 + 44(shiftcls) + 3
// 1. 进制转化
十六进制:
0 1 1 d 8 0 0 1 0 4 1 7 9 e 6 d
转二进制:
0000 0001 0001 1101 1000 0000 0000 0001 0000 0100 0001 0111 1001 1110 0110 1101
//2. 右移三位 最有右端的101移出去了,最左端补了三个零
000 0000 0001 0001 1101 1000 0000 0000 0001 0000 0100 0001 0111 1001 1110 0110 1
//3. 左移20位 17 + 3
000 0000 0000 0001 0000 0100 0001 0111 1001 1110 0110 1000 0000 0000 0000 0000 0
//4. 右移17位,回归44原来的位置
0000 0000 0000 0000 0 000 0000 0000 0001 0000 0100 0001 0111 1001 1110 0110 1000
//5. 转化成16进制
0b 0000 0000 0000 0000 0 000 0000 0000 0001 0000 0100 0001 0111 1001 1110 0110 1000
0x 0 0 0 0 0 0 0 1 0 4 1 7 9 e 6 8
0x0000000104179e68 就是终究的shiftcls
- 经过
ISA_MASK
掩码,按位与
// 1. 进制转化
# define ISA_MASK 0x00007ffffffffff8ULL // ULL 无符号长整型
十六进制:
0 0 0 0 7 f f f f f f f f f f 8
转二进制:
0000 0000 0000 0000 0111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1000
// 2.
十六进制:
0 1 1 d 8 0 0 1 0 4 1 7 9 e 6 d
转二进制:
0000 0001 0001 1101 1000 0000 0000 0001 0000 0100 0001 0111 1001 1110 0110 1101
// 3. 按位与
0000 0000 0000 0000 0111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1000
& 0000 0001 0001 1101 1000 0000 0000 0001 0000 0100 0001 0111 1001 1110 0110 1101
0000 0000 0000 0000 0000 0000 0000 0001 0000 0100 0001 0111 1001 1110 0110 1000 = 0x0000000104179e68
// 0x0000000104179e68 就是终究的shiftcls。
// 其实这儿有更简单的核算方法,这儿就不描述了(依照按位与的特性,同为真时才为真)