ISA的结构详解

2020-09-10  本文已影响0人  携YOU手同行

一,isa 的作用

在iOS开发过程中,我们知道,任何一个对象都有一个isa,通过isa 指向,可以找到父类,以及根元类的相关的实例方法,在iOS开发过程中isa的作用可以说非常重要,不可替代,因为有了iOS,系统才能才是实现相关的runtime、objc_msgSend,以及IMP 的映射。

例如,我们在main.m 文件中简单声明一个类 YOPerson,还是通过我们最常用的查看底层代码的命令语句clang -rewrite-objc main.m -o main.cpp,这样会生成一个相应的 .cpp文件,我们进入到里边查看我们声明的类的整体居然是一个这样的构造。

#ifndef _REWRITER_typedef_YOPerson
#define _REWRITER_typedef_YOPerson
typedef struct objc_object YOPerson;
typedef struct {} _objc_exc_YOPerson;
#endif

struct YOPerson_IMPL {
    struct NSObject_IMPL NSObject_IVARS;
};
/* @end */
// @implementation YOPerson
// @end

我们在开发过程中知道,不管是C语言C++,还是C#,以及我们的iOS开发 结构体是可以进行继承的,只要把结构体的声明放为第一个成员变量就可以。所以我们声明的类在底层编译的时候,就是转换为相应的结构体类型。因为这样能使用 内存对齐 原则,当然在对象的存储中还使用联合体位域 的机制来优化内存。从而达到节省内存、快速定位的作用。

在我们声明的类实现的后边我们能看到有一个NSObject_IVARS;的标识,NSObject_IVARS;是什么呢,不难发现这就是我们声明的类的isa,我们全局的搜索这个关键字能看到不管是系统的类还是我们自己的声明的类都存在这样的一个标识。

带着这样的问题我们进入详细的探索~~~~~~~

二,isa的结构

part1:isa中的概念

iOS开发中的 alloc 的执行流程学习总结 这篇文章中我们已经介绍了再alloc 的过程是通过isa来具体的关联一个累的。代码如下:objc-781中的开源代码

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);
    }

再次进入

objc_object::initIsa(Class cls, bool nonpointer, bool hasCxxDtor) 
{ 
    ASSERT(!isTaggedPointer()); 
    
    if (!nonpointer) {
        isa = isa_t((uintptr_t)cls);
    } else {
        ASSERT(!DisableNonpointerIsa);
        ASSERT(!cls->instancesRequireRawIsa());

        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;

最后到

# 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)

上边是手机端的相关的结构,下边的这部分是mac。

# elif __x86_64__
#   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)

# else
#   error unknown architecture for packed isa
# endif

这就是isa的具体的结构,接下来详细分析一下相关的每一个字段的具体作用

part2:isa中的数据的变化;

前一部分我们已经介绍了 isa中所有的字段的概念和相关的作用,接下来我来分析一下isa 它是如何工作的,内存数据变化是怎么样的。

此时我们在控制台打印 po newisa,打印出的结果如下

图片.png

此时我们能看到,nonpointer = 0, magic = 0'cls = nil,bits = 0等一切都是空的信息

赋值过后我们能看到相应的地方都有值了,nonpointer = 1, magic = 59cls = 0x001d800000000001,bits = 8303511812964353

nonpointer = 1 这个是我们自己定义的类,值为1都能理解,那为什么magic 赋值后是59?

然后我们打开计算器,把相应的cls 地址输入计算器,得出的结果是


图片.png

从图中我们看到的47位正好就是magic的地址,0011 1011 二进制正好就是59;也就是magic的值,但是为什么是47位呢?接下来我们来分析一下 isa位的情况。

part3:isa 的 的分配

我们在开发过程中知道isa指针占用8个字节。也就是64位,在根据isa的分配

图片.png
我们可以清楚的知道isa 中各个字段对应的起始位置和占用多少位的情况。
图片.png

这就是isa中的位占据大致图。没有标准的画图工具,多多包涵。

part4:isa的shiftcls如果包涵一个类的信息

通过文章和代码示例,我们知道shiftclsisa 中最重要的一部分,占据内存最大,几乎包涵了整个类的所有信息,我们怎么去验证在 shiftcls 中能找到整个类的信息呢?

我们知道,shiftcls 是从#3到#46的位置,所以为了方便我们右移3位,然后在左移17位,就能完整的得到我们想要的结果。

       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;

打印结果是


图片.png

(lldb) p (uintptr_t)cls >> 3;
(uintptr_t) $6 = 536872044

图片.png

此处我们用。po

(lldb) po 0x001d800100002365 & 0x0000000ffffffff8ULL
结果是 YOPerson

然后我们用isa 指针进行左移右移,操作

(lldb) p/x 0x001d800100002365 >> 3
结果是 $3 = 0x0003b0002000046c

再将$3 右移 20位

(lldb) p/x 0x001d800100002365 << 20
结果是 $4 = 0x0002000046c00000

再将$4左移17位

(lldb) p/x 0x0002000046c00000 >> 17
结果是 $5 = 0x001d800100002365

就可以完整的得到ISA的指针地址,
这就是为什么shiftcls 是整个ISA的最核心的存在的原因,

总结

这就是整个ISA的内部结构过程,当然只是个人的理解,有什么问题请多多指教,

上一篇下一篇

猜你喜欢

热点阅读