iOS底层探索--isa结构分析

750 阅读5分钟

小谷iOS底层博客合集

  • 当我们要了解一个东西的时候,往往会带着疑问,那么今天我们了解下传说中的isa,当然也要带着疑问:isa是什么?存了啥?啥结构?
  • OK,那我们开始探索了!

1. 准备工作

在探索isa之前。我来开一波上帝视角:要先知道联合体位域(isa有用到)。

  • 在日常iOS开发过程中,基本是不会用到的。所以大多数人不了解。这也给探索底层增加了困难。 我会简单的阐述下、如果想知道深入了解的话我写了另一篇博客:联合体与位域 希望对大家有帮助吧

1.1. 联合体与位域

  • 联合体所有成员共用一块内存,牵一而动全身!,成员之间是互斥的!修改一个成员会影响其他成员!
  • 位域 简单理解就是在上存储(我文采有限。。慢慢看下去就知道什么意思了,也可以看下联合体与位域

1.2. 所需条件

2. 开始探索

  • 准备工作做完之后--开始深入探索!

2.1. 找出isa

  • 每个类对象都有isa,我们现在不知道这个是什么?所以我们心啊写点代码把这个东西搞错来:

  • 每个对象的本质其实也是一个结构体(我会在下面扩展中证明),我们可以找到其中的isa.不过isa中到底存了些啥子呢。。他是怎么关联类的?怎么存的?接下来开始探索过程。。

2.2. 追寻isa创建的地方

  • 我们设置断点。一步一步探索如图三所示:

  • 最终走的方法是 objc_object::initIsa , 我们主要研究的就是这个东西。

  • 这段代码是这样的:


inline void 
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;
        // 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

        // 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;
    }
}
  • 除系统类之外,所有基本都是nonpointer。这个地方你就可以验证了。我们首先要确定,这个cls是我们要探索的类:

  • initIsa方法中我画了一个流程图,大家先观察下他是如何运行的!

2.3. isa结构剖析

  • 我们已经知道isa的创建赋值流程。那么我们有其他疑问了:isaisa_t的结构。那么isa_t是什么?不急,我们点进去看看~

  • 兄弟们!上帝视角还是很爽的啊。这不就是联合体位域吗。

  • 当然!又有疑问了:赋值的什么?怎么赋值的?

  • 好吧! 我又做了一个图(这图可能没啥意义{我觉得大家都知道的!!})

  • 然后,我们知道了isa结构,isa里面存了啥

#   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
  • 这就是isa的结构了,不过这些东西都是什么呢?

2.4 结构解释

  • nonpointer:表示是否对 isa 指针开启指针优化 0:纯isa指针,1:不止是类对象地址,isa 中包含了类信息、对象的引用计数等.

  • has_assoc:关联对象标志位,0没有,1存在

  • has_cxx_dtor: 该对象是否有 C++ 或者 Objc 的析构器,如果有析构函数,则需要做析构逻辑, 如果没有,则可以更快的释放对象

  • shiftcls:存储类指针的值。开启指针优化的情况下,在 arm64 架构中有 33 位用来存储类指针.

  • magic:用于调试器判断当前对象是真的对象还是没有初始化的空间

  • weakly_referenced:志对象是否被指向或者曾经指向一个 ARC 的弱变量, 没有弱引用的对象可以更快释放

  • deallocating:标志对象是否正在释放内存

  • has_sidetable_rc:当对象引用计数大于 10 时,则需要借用该变量存储进位

  • extra_rc:当表示该对象的引用计数值,实际上是引用计数值减 1, 例如,如果对象的引用计数为 10,那么 extra_rc 为 9。如果引用计数大于 10, 则需要使用到下面的 has_sidetable_rc。

  • 大家也都理解位域了,有个图比较形象(我偷得!):

由于我是Mac运行的,所以走得是x86的,iPhone手机的话会走arm64的,其实都差不多。

2.4. 赋值代码

  • 先看赋值代码
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;
  • 好像shiftcls 就是关键。那我们岂不是要看下这是个什么东西。。而且为啥要右移3位?
  • 首先我们 po (uintptr_t)cls --> XGPerson
  • 这。这TM不就是我们想要的类么。
  • 为啥要右移3位呢?解释:上面说的isa结构:前三位有nonpointerhas_assochas_cxx_dtor,占了3位!!

2.5. 逆推(不要乱想😜)

  • 继续断点调试吧!。
  • 赋值完成后,我们来看看这个shiftcls
  • 由于我这个是在x86 下,那么我就把,objisa--> 右移3位、左移20位、在右移17位(说明:目的是把除isashiftcls之外都清零)

大家不要觉得这个位移麻烦,其实就是右移3位,再左移3位(恢复位置),再左移17位(把shiftcls左边清0),再右移17位(恢复位置)

结果:

逆推完成!!

3. 扩展clang

  • clang.对于大多数iOS开发者应该是不陌生的。不过写博客么。我肯定要说一下子(因为我陌生啊。😭)

  • 1.Clang是一个C语言、C++、Objective-C语言的轻量级编译器。源代码发布于BSD协议下。 Clang将支持其普通lambda表达式、返回类型的简化处理以及更好的处理constexpr关键字

  • 2.Clang是一个由Apple主导编写,基于LLVM的C/C++/Objective-C编译器

  • 3.还是直接说怎么用比较靠谱!!我又偷了一波图:

通过第一条命令生成main.cpp!就可以知道上面说的:对象的本质是个结构体了!!