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


本页所使用的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

文章作者: 李佳
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 李佳 !
评论
 上一篇
2020新年感想 2020新年感想
2020年是一个闰年,第一天从星期三开始。 今天看了小羊肖恩大电影,故事讲得真好看,小朋友全程开心,全情投入,大朋友也觉得耳目一新,深受感动。 这是我们带他看的第一部电影,新的一年,从第一开始。 用一句涂鸦来鼓舞自己 宁愿最后徒劳无
2020-01-02 李佳
下一篇 
OC底层研究2--内存对齐原理 OC底层研究2--内存对齐原理
本页所使用的objc runtime 756.2,来自GITHUB 开始继续学习研究OC源码,今天看的是OC的内存原理,以及对齐原理的分析。 1.概念内存对齐在数据结构中,是比较基础也比较重要的一环,对于iOS开发,研究底层更免不了要了
2019-12-22 李佳
  目录