1:准备工作
isa是什么?
isa是一个Class类型的指针,每个实例对象的第一个成员变量就是isa指针,他指向对象的类,而Class里也有个isa的指针, 指向meteClass(元类)。
下载好objc的源码,然后我们在源码里找到关于isa的部分.
根据我之前的文章juejin.cn/post/698512… 我们知道alloc在底层的执行流程如下:
首先在源码中找到
_class_createInstanceFromZone这个方法:
在这个方法里面,我注意到了两行代码分别是:
obj->initInstanceIsa(cls, hasCxxDtor);obj->initIsa(cls);在然后在源码里找到第1个方法initInstanceIsa. 发现这个方法的本质也是执行方法2initIsa接下来找到
initIsa, 我们来探索一下:
从方法
initIsa中我们可以看出isa的本质是isa_t类型. initIsa方法会将obj与class进行绑定. 进入到isa_t里面,发现isa_t的本质是一个联合体,如下图所示:
2: 关于ISA_BITFIELD
-
我们在分析了OC对象的本质之后,知道了对象的内部,都至少存在一个isa成员变量,isa占用的空间大小为8字节,也就是8x8=64位。
-
如果我们把这些空间都用于存放指针,那么造成的空间浪费是巨大的。而类中除了指针,还可能有其他的东西需要进行存储,如果能够对这8字节空间进行一系列优化,就可以节省很多的内存开销。
-
在
isa_t的定义中, 我发现里面有一个结构体里面存放了ISA_BITFIELD和cls和bits. 其中cls和bits作用如下: -
cls:是Class类型的指针变量,指向的是对象的类。
-
bits:是结构体位域指针。 然后我们点进去看一下
ISA_BITFIELD的定义如下:
# if __arm64__
# define ISA_MASK 0x0000000ffffffff8ULL
# define ISA_MAGIC_MASK 0x000003f000000001ULL
# define ISA_MAGIC_VALUE 0x000001a000000001ULL
# define ISA_HAS_CXX_DTOR_BIT 1
# define ISA_BITFIELD
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 unused : 1;
uintptr_t has_sidetable_rc : 1;
uintptr_t extra_rc : 19
# define RC_ONE (1ULL<<45)
# define RC_HALF (1ULL<<18)
# endif
# elif __x86_64__
# define ISA_MASK 0x00007ffffffffff8ULL //掩码
# define ISA_MAGIC_MASK 0x001f800000000001ULL
# define ISA_MAGIC_VALUE 0x001d800000000001ULL
# define ISA_BITFIELD
uintptr_t nonpointer : 1; // 是否为纯指针
uintptr_t has_assoc : 1; // 关联对象
uintptr_t has_cxx_dtor : 1; // C++析构函数
uintptr_t shiftcls : 44; /*MACH_VM_MAX_ADDRESS 0x7fffffe00000*/
// 类的指针地址
uintptr_t magic : 6;
uintptr_t weakly_referenced : 1; // 弱引用
uintptr_t deallocating : 1;
uintptr_t has_sidetable_rc : 1; // 散列表
uintptr_t extra_rc : 8 // 引用计数
# define RC_ONE (1ULL<<56)
# define RC_HALF (1ULL<<7)
可以看到ISA_BITFIELD宏在内部分别定义了arm64位架构(iOS)和x86_64架构(macOS)的掩码和位域.
下面我们记录一下ISA_BITFIELD中不同位域的作用, 以arm64举例.
- 0号位:
nonpointer表示是否对 isa 指针开启指针优化
0:纯isa指针,存储着class, Meta-Class 对象的内存地址
1:优化过的isa指针, 不止是类对象地址, 还包含了类信息、对象的引用计数等
- 1号位:
has_assoc关联对象标志位,0没有,1存在, 如果没有关联过,释放时会更快 - 2号位:
has_cxx_dtor该对象是否有 C++ 或者 Objc 的析构器,如果有析构函数,则需要做析构逻辑, 如果没有,则可以更快的释放对象。 - 3~35号位:
shiftcls存储类指针的值。开启指针优化的情况下,在 arm64 架构中有 33 位用来存储类指针。 - 36~41号位:
magic用于调试器判断当前对象是真的对象 还是没有初始化的空间 - 42号位:
weakly_referenced标志对象是否被指向或者曾经指向一个 ARC 的弱变量,没有弱引用的对象可以更快释放 - 43号位:
deallocating标志对象是否正在释放内存 - 44号位:
has_sidetable_rc当对象引用计数大于 10 时,则需要借用该变量存储进位 - 35~64号位:
extra_rc当表示该对象的引用计数值,实际上是引用计数值减 1, 例如,如果对象的引用计数为 10,那么 extra_rc 为 9。如果引用计数大于 10, 则需要使用到下面的 has_sidetable_rc。
2: OC 中对象的分类
-
Instance 对象(实例对象) 实例对象在内存中存储的信息包括:
- isa指针
- 其他成员变量
-
Class 对象(类对象) 每个类在内存中有且仅有一个 Class 对象。 Class 对象在内存中存储的信息包括:
- isa指针
- superClass
- 属性方法
- 对象方法信息
- 协议信息成员变量信息
-
Meta-Class 对象(元类对象) 每个类在内存中也是有且仅有一个 Meta-Class 对象。
objectMetaClass是NSObject的 meta-class 对象(元类对象)。
Class objectMetaClass = object_getClass([NSObject class]); // Runtime API
// 注:以下获取的是 objectClass 是 Class 对象,并不是 Meta-Class 对象
Class objectClass = [[NSObject class] class];
Meta-Class 对象在内存中存储的信息包括:
- isa指针
- superClass
- 类方法信息
3:isa的指针和Class的关联
先来看一下initIsa的源码:
inline void
objc_object::initIsa(Class cls, **bool** nonpointer, **bool** hasCxxDtor)
{
ASSERT(!isTaggedPointer());
if (!nonpointer) {
isa = isa_t((uintptr_t)cls);
} else {
ASSERT(!DisableNonpointerIsa);
ASSERT(!cls->instancesRequireRawIsa());
isa_t newisa(0);
#if SUPPORT_INDEXED_ISA
ASSERT(cls->classArrayIndex() > 0);
newisa.bits = ISA_INDEX_MAGIC_VALUE;
// isa.magic is part of ISA_MAGIC_VALUE
// isa.nonpointer is part of ISA_MAGIC_VALUE
newisa.has_cxx_dtor = hasCxxDtor;
newisa.indexcls = (uintptr_t)cls->classArrayIndex();
#else
newisa.bits = ISA_MAGIC_VALUE;
// isa.magic is part of ISA_MAGIC_VALUE
// isa.nonpointer is part of ISA_MAGIC_VALUE
newisa.has_cxx_dtor = hasCxxDtor;
newisa.shiftcls = (uintptr_t)cls >> 3;
#endif
// This write must be performed in a single store in some cases
// (for example when realizing a class because other threads
// may simultaneously try to use the class).
// fixme use atomics here to guarantee single-store and to
// guarantee memory order w.r.t. the class index table
// ...but not too atomic because we don't want to hurt instantiation
isa = newisa;
}
}
在源码中我们可以看到initIsa主要做了两个事:
- 1: 通过
cls初始化isa如果是非nonpointer,代表普通的指针,存储着Class、Meta-Class对象的内存地址信息 - 2: 通过
bits初始化isa如果是nonpointer,则会进行一系列的初始化操作.其中的newisa.shiftcls = (uintptr_t)cls >> 3;中的shiftcls存储着Class、Meta-Class对象的内存地址信息
验证1 :
从上图可以看出:
-
cls 由默认值,变成了LGPerson,将isa与cls完美关联
-
shiftcls由0变成了536875123 关于这一过程回答两个问题:
-
1:为什么在
shiftcls赋值时需要类型强转?shiftcls = (uintptr_t)newCls >> 3;答: 因为因为内存的存储不能存储字符串,机器码只能识别 0 、1这两种数字,所以需要将其转换为uintptr_t数据类型,这样shiftcls中存储的类信息才能被机器码理解, 其中uintptr_t是long。
-
2: 为什么需要右移3位?
答: 主要是由于shiftcls处于isa指针地址的中间部分,前面还有3个位域,为了不影响前面的3个位域的数据,需要右移将其抹零。
验证2 通过isa & ISA_MSAK:
编写代码,然后在控制台打印对象和isa的地址:
从上图可以看出:
- cls 由默认值,变成了LGPerson,将isa与cls完美关联
- shiftcls由0变成了536871965
- 最后可以得出结论: 对象的
地址=isa的地址&ISA_MASK
验证3: 通过object_getClass
通过查看object_getClass的源码实现,同样可以验证isa与类关联的原理,有以下几步:
- main中导入#import <objc/runtime.h>
- 搜索
object_getClass(最后定位到objc-class.mm这个文件里.
内部调用了getIsa();这个方法
调用流程图如下:
我们看看
isa_t::getClass(MAYBE_UNUSED_AUTHENTICATED_PARAM **bool** authenticated) {这个方法里面做了什么?
然后我注意到在
else流程中,拿到isa的bits这个位,再 & ISA_MASK
验证4: 通过位运算
- 在
_class_createInstanceFromZone方法,此时cls与isa已经关联完成 由ISA_BITFIELD的定义可知, 在x86_64的电脑上, 3~47 好内存里面放着shiftcls如下图所示:
我们如果想得到
shiftcls的地址 则需要将shiftcls左右两侧都清0. 清0过程如下图所示:
这一过程也可以通过在
_class_createInstanceFromZone这个方法中使用LLDB观测出来.