本页所使用的objc runtime 756.2,来自GITHUB
开始继续学习研究OC源码,这次研究的是isa的初始化和指向分析。
1. 概念
什么是isa
看看苹果文档的介绍:
isa
A Pointer to the class definition of which this object is an instance.
isa : 一个指向该对象的类的指针。
打开Xcode,找到objc.h,我们可以看看到如下代码
|
可以看出,Class 是一个objc_class 类型的结构体。
而id类型,则是objc_object 类型的结构体.
2. isa 的初始化
在此之前,先回顾一下对象初始化的流程图
在这里,初始化实例的isa,其中 cls
为初始化的类对象,hasCxxDtor
即为是否含有C++的析构器。
我们进入 initIsa(cls, true, hasCxxDtor)
这个函数,看看内部实现了什么
2.1 isa非nonpointer
if (!nonpointer) { |
nonpointer概念:
表示是否对isa 指针开启指针优化
0: 纯isa指针
1: 不止是类对象的地址,还包含类信息、对象的引用计数等。
此时,如果为纯isa指针,将当前类 cls
赋值给 isa
的绑定属性 cls
为什么有这个绑定属性,而isa
究竟是什么看结构呢?
点击isa.cls = cls;
中的cls
查看它的结构,如下:
union isa_t { |
可以看见isa 是一个 union,联合体,里面包含了
isa_t
初始化方法isa_t(uintptr_t value)
工厂方法Class cls
绑定属性- 结构体
ISA_BITFIELD
位域
ISA_BITFIELD概念
我们点开类型为
struct
的ISA_BITFIELD
,结构如下:NONPOINTER_ISA效果图(手绘中,待补全……)
还原isa_t 的结构
我们这时发现,isa的整体结构可以替换为如下的样子:
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_BITFIELD; // defined in isa.h
};
};
nonpointer: 表示是否对 isa 指针开启指针优化 0:纯isa指针,1:不止是类对象地址**,isa** 中包含了类信息、对象的引用计数等(占1位)
has_assoc: 关联对象标志位,0没有,1存在(占1位)
has_cxx_dtor: 该对象是否有 C++ 或者 Objc 的析构器**,如果有析构函数,则需要做析构逻辑,** 如果没有**,**则可以更快的释放对象(占1位)
**shiftcls:**存储类指针的值。开启指针优化的情况下,在 arm64 架构中有 33 位用来存储类指针。(占33位)
magic:用于调试器判断当前对象是真的对象还是没有初始化的空间 weakly_referenced:志对象是否被指向或者曾经指向一个 ARC 的弱变量,没有弱引用的对象可以更快释放。(占6位)
deallocating:标志对象是否正在释放内存(占1位)
has_sidetable_rc:当对象引用计数大于 10 时,则需要借用该变量存储进位(占1位)
extra_rc:当表示该对象的引用计数值,实际上是引用计数值减 1, 例如,如果对象的引用计数为 10,那么 extra_rc 为 9。如果引用计数大于 10, 则需要使用到下面的 has_sidetable_rc。(占1位)
2.2 isa是nonpointer类型
isa_t newisa(0); |
生成新的
isa
:isa_t newisa(0);
c++ 析构器:
newisa.has_cxx_dtor = hasCxxDtor;` 表示当前对象是否有C++的析构函数(destructor),如果没有,释放时会快速的释放内存。
位域赋值
newisa.shiftcls = (uintptr_t)cls >> 3;
对存储指针的值进行右移动3位赋值。返回isa
isa = newisa;
3. isa 的指向
3.1 指向图:
关于isa的指向以及子类父类的关系,苹果官方给出了一张图如下所示:
3.2 代码分析
3.2.1 对象的isa
我们执行一项代码如下,并对该行打断点:
Person *object = [Person alloc]; |
我们知道:对象里的 isa ——指向——> 类。
3.2.2类的isa
现在我们想知道类的内存空间结构,在控制台执行如下指令x/4gx Person.class
,结果如下:
(lldb) x/4gx Person.class |
由于isa
是类对象的第一个属性,我们知道0x001d800100001109
是改对象的isa,我们看看他指向哪里呢,使用p/x
指令试试:
(lldb) p/x 0x001d800100001109 |
糟糕,查看不到结果?怎么回事?类的isa 格式需要强转,可以退一步,打印类的地址试试:
po 0x100001130 |
原来如此,在内存空间里,名为Person的类的第一个位置,指向Person类,岂不是循环指向了?
非也非也,这里指向的类,我们把它称为元类(meta-class)
类的isa ——指向——> 元类
3.2.3 元类的isa
我们现在获得元类的具体地址,找到isa
的MASK
(掩码),值为0x00007ffffffffff8
,
输入以下指令:
(lldb) p/x 0x001d800100001109 & 0x00007ffffffffff8 |
得到元类地址为:0x0000000100001108,16进制打印一下:
(lldb) x/4gx 0x0000000100001108 |
可以看到元类结构里,isa指针为 0x001d800100b390f1,继续获取它的指向,我们通过与掩码来计算:
(lldb) p/x 0x001d800100b390f1 & 0x00007ffffffffff8 |
好嘞,拿到内存指针地址为0x0000000100b390f0, 打印一下:
po 0x0000000100b390f0 |
至此,我们可以看到元类的isa
指向它的上一级元类,也就是跟元类(root meta-class),为NSObject。
所以得出: 元类的isa ——指向——> 根元类
3.2.4 根元类的isa
我们打印下根元类结构:
x/4gx 0x0000000100b390f0 |
拿到它的isa,与掩码继续进行与运算
p/x 0x001d800100b390f1 & 0x00007ffffffffff8 |
得到的结果0x0000000100b390f0,与根元类0x0000000100b390f0,完全吻合。
至此,我们得出结论:根元类的isa ——指向——> 根类NSObject。
什么?你不信,这些都是猜测,证实一下?
好的,创建如下代码
void TestNSObject(){ |
打印结果如下:
0x10066ddc0 实例对象 |
可见,除了NSObject 类是独有的创建,其他元类、根元类、根根元类,都是一样的,因为都是NSObject,所以结果得到了证明。
3.3 总结:
我们再回到这幅图,最红是这样的:
isa指向:
- 对象中的isa——> 类
- 类中的isa ——> 元类
- 元类中的isa —-> 根元类
- 根元类中的isa —-> 根元类
类继承关系:
- 子类 ———superClass——— 父类
- 父类 ———superClass——— 根元类
- 根元类 ———superClass——— NSObject
- NSObject ———superClass——— nil