在开始分析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个位域,为了避免影响它们,故右移将它们抹零。