isa的初始化&指向分析

351 阅读5分钟

上一篇文章中分析了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类的父类为NSObject
  • ZHYObject元类的父类为根元类
  • 根元类的父类为NSObject
  • NSObject的父类为nil

以上就是isa的初始化和指向分析。。