iOS底层原理探索 -- isa的本质

在我们最早学习iOS开发时,会听到alloc的方法是用来开辟内存空间的说法。
但是在我们研究alloc底层时会发现,其实alloc不仅仅开辟了类的内存空间,同时他还关联了类的信息

QQ20200910-220616.png

那么现在就来研究 alloc 过程中是如何关联类的信息的。


  • isa的关联时间

通过我们对于 alloc 源码的跟踪
当代码进入_class_createInstanceFromZone方法时生成了对象

_class_createInstanceFromZone(Class cls, size_t extraBytes, void *zone,
                              int construct_flags = OBJECT_CONSTRUCT_NONE,
                              bool cxxConstruct = true,
                              size_t *outAllocatedSize = nil)
{
    ASSERT(cls->isRealized());

    // Read class's info bits all at once for performance
    bool hasCxxCtor = cxxConstruct && cls->hasCxxCtor();
    bool hasCxxDtor = cls->hasCxxDtor();
    bool fast = cls->canAllocNonpointer();
    size_t size;

    size = cls->instanceSize(extraBytes);
    if (outAllocatedSize) *outAllocatedSize = size;

    id obj;
    if (zone) {
        obj = (id)malloc_zone_calloc((malloc_zone_t *)zone, 1, size);
    } else {
        // alloc 开辟内存的地方
        obj = (id)calloc(1, size);
    }
    if (slowpath(!obj)) {
        if (construct_flags & OBJECT_CONSTRUCT_CALL_BADALLOC) {
            return _objc_callBadAllocHandler(cls);
        }
        return nil;
    }

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

    if (fastpath(!hasCxxCtor)) {
        return obj;
    }

    construct_flags |= OBJECT_CONSTRUCT_FREE_ONFAILURE;
    return object_cxxConstructFromClass(obj, cls, construct_flags);
}

通过返回值 obj 我们知道关键方法必然是操作了obj 的方法
于是,我们如图设置断点

QQ20200910-223641.png

在对应断点打印obj的内容后
QQ20200910-223707.png

我们可以知道类信息的关联操作必然是通过obj->initInstanceIsa(cls, hasCxxDtor)或者obj->initIsa(cls)方法进行的。

  • isa的结构

通过对obj->initInstanceIsa(cls, hasCxxDtor)的源码追踪,发现其主要功能是从初始化了一个isa_t的东西,那么这个isa_t是什么?作用是什么?
首先,我们追踪断点进入方法,打印这个isa_t

QQ20200911-003022.png

可以看到isa_t的结构如图所示
同时,我们也可以通过代码发现,isa_t 其实是一个联合体union
QQ20200911-003732.png

结构体(struct)中所有变量是“共存”的——优点是“有容乃大”, 全面;缺点是struct内存空间的分配是粗放的,不管用不用,全分配。

联合体(union)中是各变量是“互斥”的——缺点就是不够“包容”; 但优点是内存使用更为精细灵活,也节省了内存空间

苹果通过联合体而非结构体的方式用来节省内存

  • 重点 - isa是如何存储信息的

从上方打印的isa_t能看出cls里的信息为 类信息
bits 中储存了什么信息?

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

这是isa的结构,分别为手机端 mac端 (不同处理器的区别)

而每一位代表的含义则如图


我们来尝试研究一下我们比较关心,也比较容易验证的shiftcls是否真的保存了类指针

我们在alloc流程中的_class_createInstanceFromZone方法中设置断点,断点时间再isa赋值后返回前

打印当前的obj 即我们alloc初始化的FQModel类的对象

QQ20200911-015518.png

以八字节分段打印地址内容
QQ20200911-015535.png

其中第一段的8个字节应当就是我们要的isa
QQ20200911-015555.png

我们当前需要取的类指针shiftcls是在[3 46]位之间
那我们将其他位运用位运算进行抹零操作
先右移3位
QQ20200911-015612.png

再左移 20(17+3)
QQ20200911-015622.png

最后让内容回到原本的[3 46]位 右移17位
QQ20200911-015634.png

最终结果,我们与当前的cls比较
QQ20200911-015655.png

确实一致,可见shiftcls确实存储了类指针

当然,并不一定每次计算都需要这么复杂,我们可以直接通过位运算与上掩码来取类指针


QQ20200911-015758.png

其原理 与运算中如果有都为1则保留1,否则保留0
1101 1000
如果我们想保留4-5位上的数字
我们可以直接用这个数与上
0001 1000

1101 1000
0001 1000
这样就可以起到将前三位和后三位的数字都置为0,保留原本4-5位上的数字


  • 关于联合体位域的应用

这个话题其实和当前isa的讨论关系不大,但是在开发过程中也是可以用到这样的思路。

比如系统权限的控制
如果一个系统有八大权限,相互不依赖和影响,那我们就可以用一个8位的2进制数,每一位代表一个权限是否拥有,这样直接比用8个属性或者枚举所有可能性来的轻便,好用。

你可能感兴趣的:(iOS底层原理探索 -- isa的本质)