本文代码运行在 Mac OS 上,也就是运行在 x86_64 架构下,对于运行在 arm64 架构下的代码大同小异
1、了解 isa 是什么
如果大家对 ObjC
的底层实现有一定了解的话,应该知道 Objective-C 对象都是 C 语言结构体,所以的对象都包含一个类型为结构体的 isa_t
,这个结构体中包含了当前对象指向的类的信息。
struct objc_object {
isa_t isa;
}
当 ObjC
为一个对象分配内存并初始化变量后,在这些对象的实例变量的结构体中第一个就是 isa
。所有继承自 NSObject
的类实例化后的对象都会包含一个类型为 isa_t 的结构体。
从上图可以看出,不只是 实例对象 会包含一个 isa
结构体,所以的 类对象 也会包含这个一个 isa
。在 ObjC
中 Class
的定义也是一个名为 objc_class
的结构体。
struct objc_class : objc_object {
isa_t isa;
Class superclass;
cache_t cache;
class_data_bits_t bits;
}
由于 objc_class
结构体继承自 objc_object
,所以在上诉代码中显示地写出了 isa_t isa
这个成员变量。
到这里,我们就可以得出一个结论:Objective-C 中类也是一个对象。
2、特殊结构体 isa_t
我们以 x86_64 架构下为例,可以在 ObjC
源码中可以看到这样的定义:
# 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; \
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)
union isa_t {
isa_t() { }
isa_t(uintptr_t value) : bits(value) { }
Class cls;
uintptr_t bits;
#if defined(ISA_BITFIELD)
struct {
ISA_BITFIELD; // defined in isa.h
};
#endif
};
isa_t
是一个 union
的结构体,也就是说其中的 cls
和 bits
共用同一块内存地址空间,而 isa
总共会占据 64 位的内存空间。
如果对
union
不太熟悉,可以参考一下这篇文章 结构体(struct)和联合体(union)的区别
以下是 isa
64位 bits
所代表的意思。
- nonpointer: 表示是否对
isa
指针开启指针优化
- 0: 纯isa指针,
- 1: 不止是类对象地址,isa 中包含了类信息、对象的引用计数等。
- has_assoc: 关联对象标志位,
- 0: 没有,
- 1: 存在。
- has_cxx_dtor: 该对象是否有
C++
或者Objc
的析构器,如果有析构函数,则需要做析构逻辑, 如果没有, 则可以更快的释放对象。- shiftcls: 存储类指针的值。开启指针优化的情况下,在
arm64
架构中有33
位用来存储类指针。- magic: 用于调试器判断当前对象是真的对象还是没有初始化的空间。
- weakly_referenced: 标志对象是否被指向或者曾经指向一个
ARC
的弱变量,
没有弱引用的对象可以更快释放。- deallocating: 标志对象是否正在释放内存。
- has_sidetable_rc: 当对象引用计数大于
10
时,则需要借用该变量存储进位。- extra_rc: 当表示该对象的引用计数值,实际上是引用计数值 减 1, 例如,如果对象的引用计数为 10,那么 extra_rc 为 9。如果引用计数大于 10, 则需要使用到上面的
has_sidetable_rc
。
3、isa 的初始化
我们可以通过 isa
初始化的方法 initIsa
来初步了解这 64 位的 bits 的作用:
inline void
objc_object::initInstanceIsa(Class cls, bool hasCxxDtor)
{
initIsa(cls, true, hasCxxDtor);
}
inline void
objc_object::initIsa(Class cls, bool nonpointer, bool hasCxxDtor)
{
if (!nonpointer) {
isa = isa_t((uintptr_t)cls);
} else {
isa_t newisa(0);
newisa.bits = ISA_MAGIC_VALUE;
newisa.has_cxx_dtor = hasCxxDtor;
newisa.shiftcls = (uintptr_t)cls >> 3;
isa = newisa;
}
}
3.1 nonpointer 和 magic
当我们对一个 ObjC
对象分配内存时,其方法调用栈中包含了上述的两个方法,这里关注的重点是 initIsa
方法,由于在 initInstanceIsa
方法中传入了 nonpointer = true
,所以,我们简化一下这个方法的实现:
inline void
objc_object::initIsa(Class cls, bool nonpointer, bool hasCxxDtor)
{
newisa.bits = ISA_MAGIC_VALUE;
newisa.has_cxx_dtor = hasCxxDtor;
newisa.shiftcls = (uintptr_t)cls >> 3;
}
对整个 isa
的值 bits
进行设置,传入 ISA_MAGIC_VALUE
:
#define ISA_MAGIC_VALUE 0x001d800000000001ULL
我们先在工程中进行源码调试,得到以下结果
nonpointer
值为1,相信大家都知道,那么 magic
的值为什么为59呢,大家就可能迷糊了,我们可以把 ISA_MAGIC_VALUE
的值输入到计算器中
然后我们输入十进制的59,然后看二进制
看到这,大家明白为什么 magic
的值为 59 了吗?
所以我们可以把它转换成二进制的数据,然后看一下哪些属性对应的位被这行代码初始化了(标记为红色):
从图中了解到,在使用 ISA_MAGIC_VALUE
设置 isa_t
结构体之后,实际上只是设置了 nonpointer
以及 magic
这两部分的值。
其中
nonpointer
表示isa_t
的类型0: 表示
raw isa
,也就是没有结构体的部分,访问对象的isa
会直接返回一个指向cls
的指针,也就是在 iPhone 迁移到 64 位系统之前时isa
的类型。union isa_t {
isa_t() { }
isa_t(uintptr_t value) : bits(value) { }
Class cls;
uintptr_t bits;
};
1: 表示当前
isa
不是指针,但是其中也有cls
的信息,只是其中关于类的指针都是保存在shiftcls
中。union isa_t {
isa_t() { }
isa_t(uintptr_t value) : bits(value) { }
Class cls;
uintptr_t bits;
#if defined(ISA_BITFIELD)
struct {
ISA_BITFIELD; // defined in isa.h
};
#endif
};
3.2 has_cxx_dtor
在设置 nonpointer
和 magic
值之后,会设置 isa
的 has_cxx_dtor
,这一位表示当前对象有 C++
或者 ObjC
的析构器(destructor),如果没有析构器就会快速释放内存。
newisa.has_cxx_dtor = hasCxxDtor;
3.3 shiftcls(重点)
在为 nonpointer
、 magic
和 has_cxx_dtor
设置之后,我们就要将当前对象对应的类指针存入 isa 结构体中了。
newisa.shiftcls = (uintptr_t)cls >> 3;
将当前地址右移三位的主要原因是用于将 Class 指针中无用的后三位清除进而减小内存的消耗,因为类的指针要按照字节(8 bits)对齐内存,其指针后三位都是没有意义的 0。
我们继续通过跟踪源码来说明 shiftcls
存储的就是类的指针信息
上图的输出结果表明,shiftcls
和 cls
已经绑定在一起了,我们继续跟踪断点来进一步说明
上图红框打印的地址,其实就是当前类的 isa
地址,继续往下走
这次我们将 isa
& ISA_MASK
得到了我们的 Class
。这也就验证了我们之前对于初始化 isa
时对 initIsa
方法的分析是正确的。它设置了 nonpointer
、magic
以及 shiftcls
。
3.4 getIsa() 方法
通过掩码的方式获取类指针:
Class object_getClass(id obj)
{
if (obj) return obj->getIsa();
else return Nil;
}
inline Class
objc_object::getIsa()
{
return ISA();
}
inline Class
objc_object::ISA()
{
return (Class)(isa.bits & ISA_MASK);
}
3.5 其它 bits
isa_t
的其他 bits,我们在前面已经简要介绍过了,这里就不再详细展开分析。