在开始分析isa
前,我们得先搞清楚一个问题:对象是什么?即对象的本质是什么?要搞清这个问题我们还得先了解一下Clang
。
一、Clang
1.Clang是什么
Clang
是⼀个由Apple
主导编写,基于LLVM
的C/C++/Objective-C
编译器。
2.Clang的使用
Clang
的使用语法有很多,这里就不一一举例了,有兴趣的可以自行搜索,今天我们用到的语法如下:
//将目标文件编译成c++文件
clang -rewrite-objc main.m -o main.cpp
同时Xcode
在安装的时候顺带安装了xcrun
命令,xcrun
命令在clang
的基础上进⾏了⼀些封装,要更好⽤⼀些,用法如下:
//模拟器
xcrun -sdk iphonesimulator clang -arch arm64 -rewrite-objc main.m -o
main-arm64.cpp
//手机
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m -o main�arm64.cpp
简单了解完Clang
,我们来使用看看吧。
二、对象的本质
1.使用上述clang
语法,我们得到了一个cpp
文件,如下图:

LGPerson
,看一下编译前后有什么不同,如下图:

LGPerson
变成了一个结构体(struct)
,并且定义的属性name
变成了结构体
的数据成员
,同时还多了一个struct NSObject_IMPL NSObject_IVARS
,并且也是一个结构体
。
4.LGPerson
继承自NSObject
,它在编译后必然也是一个结构体
,如下图:

NSObject
结构体作为LGPerson
的第一个数据成员
,并且NSObject
中有一个isa
数据成员,这个属于伪继承
,意味着LGPerson
拥有NSObject
中的所有成员,所以NSObject_IVARS
就等效于isa
。
总结:对象的本质
其实就是结构体
,LGPerson
中的isa
继承自NSObject
的isa
。
三、isa分析
在alloc探索中,核心三步中有一个initInstanceIsa
方法,进入源码,发现调用的是initIsa
方法,如下图:

isa
是通过isa_t
类型初始化的,那isa_t
又是什么类型的呢?带着疑问,我们进一步探索,发现isa_t
是一个联合体(union)
,那联合体
又是什么呢?
1.联合体(union)
联合体(union)
也叫共用体
和结构体(struct)
一样都是构造数据类型。
结构体
所有变量
都是共存
的,它们占用不用的内存
,优点:容量大
、成员间互不影响
;缺点:不管用不用,都会为成员
分配内存,浪费内存
。
联合体
所有变量
都是互斥
的,它们共用一段内存
,优点:节省内存空间
;缺点:包容性弱
,共用体
使用了内存覆盖技术
,同一时刻只能保存一个成员的值
,即如果对新的成员赋值
,就会把原来成员的值覆盖掉
。
2.isa_t
isa_t
的结构如下图:

联合体
中定义了两个成员cls
和bits
和一个结构体位域ISA_BITFIELD
(用来存放类信息
和其他信息
)。
cls
和bits
说明这里有两种初始化方式:
1.通过cls
初始化:即isa的初始化
图中的isa = isa_t((uintptr_t)cls)
2.通过bits
初始化:即else
部分
3.位域
有些数据在存储时并不需要占用一个完整的字节,只需要占用一个或几个二进制位即可。基于这种的数据结构,就是位域
。
这里ISA_BITFIELD
就是一个位域
,它有两个版本,分别对应__arm64__
和__x86_64__
,即iOS
和macOS
,如下图:

nonpointer
:表示是否对 isa 指针开启指针优化0
:纯isa指针1
:不⽌是类对象地址
,isa 中包含了类信息
、对象的引⽤计数
等
2.has_assoc
:是否关联对象标志位
0
:没有
1
:存在
3.has_cxx_dtor
:该对象是否有 C++ 或者 Objc 的析构器
如果有
析构函数(类似于OC中的dealloc
),则需要做析构逻辑
,
如果没有
,则可以更快的释放对象
4.shiftcls
: 存储类指针的值
arm64
:开启指针优化的情况下,在 arm64 架构中有33
位⽤来存储类指针
x86_64
:有44
位用来存储类指针
5.magic
:⽤于调试器判断当前对象是真的对象
还是没有初始化的空间
6.weakly_referenced
:对象是否被指向
或者曾经指向⼀个ARC的弱变量
没有弱引⽤
的对象可以更快释放
7.deallocating
:标志对象是否正在释放内存
8.has_sidetable_rc
:当对象引⽤技术⼤于10
时,则需要借⽤该变量存储进位
9.extra_rc
:当表示该对象的引⽤计数值
,实际上是引⽤计数值减 1
如果对象的引⽤计数为 10
,那么 extra_rc
为 9
如果引⽤计数⼤于 10
,则需要使⽤到has_sidetable_rc
isa
的存储分布如下图:

4.分析
我们跟随代码来到initIsa
方法中,如下图:

nonpointer
参数为true
,这里执行了else
的操作,创建了newisa
联合体,打印结果如下图:

ISA_MAGIC_VALUE
给bits
进行赋值,看一下赋值后的结果,如下图:

cls
赋值0x001d800000000001
,因为在给bits
赋值时,会为cls
追加默认值
,但是为什么magic
赋值了59
呢?
打开计算器并输入0x001d800000000001
,从47
位开始读取6
位,将59
转二进制
,发现都为111011
,如下图:

二进制
,并从47
位开始读取6
位,再转换成十进制
。但是为什么是从47
开始呢?
根据赋值后的newisa
图,前面有4个位域
(nonpointer
占1位,has_assoc
占1位,has_cxx_dtor
占1位,shiftcls
占44位)共占了47位
,所以magic
从47
位开始。
3.通过newisa.shiftcls = (uintptr_t)cls >> 3
关联类信息。

shiftcls
已经赋上了右移后得到的值536871986
,同时cls
也从默认值
变成了LGPerson
,实现了isa与类的关联
。
这里需要注意两点:
为什么shiftcls需要强转类型?
因为此时,cls
可能是个字符串
,直接存储会导致无法识别,所以强转成uintptr_t
(unsigned long
类型)
为什么需要右移3位
因为shiftcls
前面还有3
个位域,为了避免影响它们,故右移
将它们抹零
。
网友评论