OC底层研究3--isa的初始化和指向分析

584 阅读6分钟

本页所使用的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,我们可以看看到如下代码

#if !OBJC_TYPES_DEFINED
/// An opaque type that represents an Objective-C class.
/// 一个展示OC类的未知的类型
typedef struct objc_class *Class;

/// Represents an instance of a class.
/// 展示一个类的实例
struct objc_object {
    Class _Nonnull isa  OBJC_ISA_AVAILABILITY;
};

/// A pointer to an instance of a class.
/// 一个指向类的实例的指针
typedef struct objc_object *id;
#endif

可以看出,Class 是一个objc_class 类型的结构体。

而id类型,则是objc_object 类型的结构体.

2. isa 的初始化

在此之前,先回顾一下对象初始化的流程图

在这里,初始化实例的isa,其中 cls 为初始化的类对象,hasCxxDtor 即为是否含有C++的析构器。

我们进入 initIsa(cls, true, hasCxxDtor) 这个函数,看看内部实现了什么

2.1 isa非nonpointer

if (!nonpointer) {
    isa.cls = cls;
} 
  • nonpointer概念:

    表示是否对isa 指针开启指针优化

0: 纯isa指针

1: 不止是类对象的地址,还包含类信息、对象的引用计数等。

此时,如果为纯isa指针,将当前类 cls 赋值给 isa 的绑定属性 cls

为什么有这个绑定属性,而isa究竟是什么看结构呢?

点击isa.cls = cls;中的cls查看它的结构,如下:

union isa_t {
    isa_t() { }					// isa 初始化方法
    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 是一个 union,联合体,里面包含了

  • isa_t 初始化方法
  • isa_t(uintptr_t value) 工厂方法
  • Class cls 绑定属性
  • 结构体ISA_BITFIELD位域
  1. ISA_BITFIELD概念

    我们点开类型为structISA_BITFIELD,结构如下:

    # if __arm64__
    #   define ISA_MASK        0x0000000ffffffff8ULL
    #   define ISA_MAGIC_MASK  0x000003f000000001ULL
    #   define ISA_MAGIC_VALUE 0x000001a000000001ULL
    #   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 deallocating      : 1;                                       \
          uintptr_t has_sidetable_rc  : 1;                                       \
          uintptr_t extra_rc          : 19
    #   define RC_ONE   (1ULL<<45)
    #   define RC_HALF  (1ULL<<18)
    
  2. NONPOINTER_ISA效果图(手绘中,待补全......)

  3. 还原isa_t 的结构

    我们这时发现,isa的整体结构可以替换为如下的样子:

    union isa_t {
        isa_t() { }
        isa_t(uintptr_t value) : bits(value) { }
    
        Class cls;
        uintptr_t bits;
    #if defined(ISA_BITFIELD)
        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
        };
    #endif
    };
    
  • 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_rc9。如果引用计数大于 10, 则需要使用到下面的 has_sidetable_rc。(占1位)

2.2 isanonpointer类型

        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

  • 生成新的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
0x100001130: 0x001d800100001109 0x0000000100b39140
0x100001140: 0x0000000101a46ed0 0x0000000200000007

由于isa是类对象的第一个属性,我们知道0x001d800100001109是改对象的isa,我们看看他指向哪里呢,使用p/x 指令试试:

(lldb) p/x 0x001d800100001109
(long) $16 = 0x001d800100001109

糟糕,查看不到结果?怎么回事?类的isa 格式需要强转,可以退一步,打印类的地址试试:

po 0x100001130
Person

原来如此,在内存空间里,名为Person的类的第一个位置,指向Person类,岂不是循环指向了?

非也非也,这里指向的类,我们把它称为元类(meta-class)

类的isa ——指向——> 元类

3.2.3 元类的isa

我们现在获得元类的具体地址,找到isaMASK(掩码),值为0x00007ffffffffff8

输入以下指令:

(lldb) p/x 0x001d800100001109 & 0x00007ffffffffff8
(long) $17 = 0x0000000100001108
(lldb) po 0x0000000100001108
Person

得到元类地址为:0x0000000100001108,16进制打印一下:

(lldb) x/4gx 0x0000000100001108
0x100001108: 0x001d800100b390f1 0x0000000100b390f0
0x100001118: 0x0000000100f5a480 0x0000000400000007

可以看到元类结构里,isa指针为 0x001d800100b390f1,继续获取它的指向,我们通过与掩码来计算:

(lldb) p/x 0x001d800100b390f1 & 0x00007ffffffffff8
(long) $21 = 0x0000000100b390f0

好嘞,拿到内存指针地址为0x0000000100b390f0, 打印一下:

po 0x0000000100b390f0
NSObject

至此,我们可以看到元类的isa指向它的上一级元类,也就是跟元类(root meta-class),为NSObject。

所以得出: 元类的isa ——指向——> 根元类

3.2.4 根元类的isa

我们打印下根元类结构:

x/4gx 0x0000000100b390f0
0x100b390f0: 0x001d800100b390f1 0x0000000100b39140
0x100b39100: 0x0000000101a47020 0x0000000500000007

拿到它的isa,与掩码继续进行与运算

p/x 0x001d800100b390f1 & 0x00007ffffffffff8
(long) $27 = 0x0000000100b390f0

得到的结果0x0000000100b390f0,与根元类0x0000000100b390f0,完全吻合。

至此,我们得出结论:根元类的isa ——指向——> 根类NSObject

什么?你不信,这些都是猜测,证实一下?

好的,创建如下代码

void TestNSObject(){
    // NSObject实例对象
    NSObject *object1 = [NSObject alloc];
    // NSObject类
    Class class = object_getClass(object1);
    // NSObject元类
    Class metaClass = object_getClass(class);
    // NSObject根元类
    Class rootMetaClass = object_getClass(metaClass);
    // NSObject根根元类
    Class rootRootMetaClass = object_getClass(rootMetaClass);
    NSLog(@"\n%p 实例对象\n%p 类\n%p 元类\n%p 根元类\n%p 根根元类",object1,class,metaClass,rootMetaClass,rootRootMetaClass);
}

打印结果如下:

0x10066ddc0 实例对象
0x7fff9294a118 类
0x7fff9294a0f0 元类
0x7fff9294a0f0 根元类
0x7fff9294a0f0 根根元类

可见,除了NSObject 类是独有的创建,其他元类、根元类、根根元类,都是一样的,因为都是NSObject,所以结果得到了证明。

3.3 总结:

我们再回到这幅图,最红是这样的:

  • isa指向:

    • 对象中的isa——> 类
    • 类中的isa ——> 元类
    • 元类中的isa ----> 根元类
    • 根元类中的isa ----> 根元类
  • 类继承关系:

    • 子类 ———superClass——— 父类
    • 父类 ———superClass——— 根元类
    • 根元类 ———superClass——— NSObject
    • NSObject ———superClass——— nil