上一篇文章中分析了alloc的流程,其中的isa的初始化没有进行探索,本篇文章将继续探索isa的初始化和指向分析。
环境:xcode 11.5
源码:objc4-781
在alloc过程中calloc方法会申请一定的内存空间来存放对象,那么存放的对象是什么呢?
static ALWAYS_INLINE id
_class_createInstanceFromZone(Class cls, size_t extraBytes, void *zone,
int construct_flags = OBJECT_CONSTRUCT_NONE,
bool cxxConstruct = true,
size_t *outAllocatedSize = nil) {
...
//此时打印obj,返回0x0000000101134ff0
if (!zone && fast) {
obj->initInstanceIsa(cls, hasCxxDtor);
} else {
// Use raw pointer isa on the assumption that they might be
// doing something weird with the zone or RR.
obj->initIsa(cls);
}
//此时打印obj,返回<ZHYObject: 0x101134ff0>
...
通过上述打印可以看出,在经历了initInstanceIsa之后,系统才会把特定的内存空间和我们熟悉的类关联起来。我们也知道,一个实例的isa指向它的类,而一个类的isa指向它的元类。那么isa内部是怎么实现的?结构又是什么样子的呢?首先看一下initInstanceIsa的内部实现。
inline void
objc_object::initInstanceIsa(Class cls, bool hasCxxDtor)
{
assert(!cls->instancesRequireRawIsa());
assert(hasCxxDtor == cls->hasCxxDtor());
initIsa(cls, true, hasCxxDtor);
}
inline void
objc_object::initIsa(Class cls, bool nonpointer, bool hasCxxDtor)
{
...
isa_t newisa(0);
...
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;
// 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方法。通过阅读源码可以发现,isa_t是我们的需要探索的核心部分。
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是一个联合体类型,里面同时定义了一个位域ISA_BITFIELD。对于联合体、结构体和位域的相关知识可以阅读结构体(Struct)、联合体(Union)和位域。
查看此位域的内部结构:
只查看x86下的定义
# elif __x86_64__
# define ISA_MASK 0x00007ffffffffff8ULL
//二进制表示为:0b0000000000000000011111111111111111111111111111111111111111111000
# define ISA_MAGIC_MASK 0x001f800000000001ULL
//二进制表示为:0b0000000000011111100000000000000000000000000000000000000000000001
# define ISA_MAGIC_VALUE 0x001d800000000001ULL
//二进制表示为:0b0000000000011101100000000000000000000000000000000000000000000001
# define ISA_BITFIELD
uintptr_t nonpointer : 1;//表示是否对 isa 指针开启指针优化 0:纯isa指针,1:不止是类对象地址,isa 中包含了类信息、对象的引用计数等
uintptr_t has_assoc : 1;//关联对象标志位,0没有,1存在
uintptr_t has_cxx_dtor : 1;//该对象是否有 C++ 或者 Objc 的析构器,如果有析构函数,则需要做析构逻辑,如果没有,则可以更快的释放对象
uintptr_t shiftcls : 44; /*MACH_VM_MAX_ADDRESS 0x7fffffe00000*/ 存储类指针的值
uintptr_t magic : 6;//用于调试器判断当前对象是真的对象还是没有初始化的空间
uintptr_t weakly_referenced : 1;//对象是否被指向或者曾经指向一个 ARC 的弱变量,
没有弱引用的对象可以更快释放
uintptr_t deallocating : 1;//标志对象是否正在释放内存
uintptr_t has_sidetable_rc : 1;//当对象引用技术大于 10 时,则需要借用该变量存储进位
uintptr_t extra_rc : 8;//axisPointer
...
#endif
可以发现isa_t的大小为8字节即64位。
回到上文的initIsa方法中,针对isa初始化的主要代码如下:
isa_t newisa(0); //初始化一个isa_t的联合体
newisa.bits = ISA_MAGIC_VALUE;
newisa.has_cxx_dtor = hasCxxDtor;
newisa.shiftcls = (uintptr_t)cls >> 3;//存储对应的类信息
接下来我们通过lldb验证一下isa中类的存储和我们通过源码阅读发现的位置是否一样。
ZHYObject *object1 = [[ZHYObject alloc]init];
通过一系列的位移操作最终取出了
isa_t中4-47位。与ZHYObject类的内存进行对比发现一模一样。
除此之外,在之前的isa_t位域定义中还有一个名为ISA_MASK的宏,作用是作为掩码,我们可以直接通过与ISA_MASK进行位的&运算,直接得出对应的值,和ZHYObject类的内存是一样的。
不知道大家有没有这个疑问,为什么代表
cls的指针是从isa_t联合体中的第4位开始存放?这就意味着通过掩码ISA_MASK得到的shiftcls的后三位会一直为0,这是因为我们开辟的内存是以8字节对齐,就意味着内存地址只能是8的倍数,即后三位为000。
接下来是这张很著名的图了
这张图表示了ios中类的实例、类对象、以及元类和它们的父类之间的一个关系。还以我们的
ZHYObject为例,我们来进行验证。
补充知识点,x/4gx打印出的内存结构
struct objc_class : objc_object {
// Class ISA;
Class superclass;
.....
上图中我们从一个
ZHYObject的实例出发,通过isa一步一步向上查找,顺序为实例->类->元类->根元类,通过内存作对比发现:
实例的isa指向了ZHYObject元类ZHYObject元类的isa指向了根元类根元类的isa指向了根元类
同时,因为obcj_class结构中,第二个8字节代表的是它的父类,因此我们也可以验证出关于父类的指向也是正确的,即:
ZHYObject类的父类为NSObjectZHYObject元类的父类为根元类根元类的父类为NSObjectNSObject的父类为nil
以上就是isa的初始化和指向分析。。