OC底层原理--isa结构分析

isa对于大家来说应该并不陌生,不管是各个公司的面试题或者说是平时的开发当中都会经常被提及,另外在我们之前对alloc的源码分析时也发现,最后一步obj->initInstanceIsa(cls, hasCxxDtor)便是对isa的初始化.今天我们就跟随源码一起来看一看isa到底是个怎样的存在

联合体位域

在讲isa之前,我们先来学习一个概念:联合体位域.

联合体与结构体

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

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

举例说明:
存在如下四个属性

@property (nonatomic, assign) int front; // 1:正在向前 2:没有向前
@property (nonatomic, assign) int back;  // 1:正在向后 2:没有向后
@property (nonatomic, assign) int left;  // 1:正在向左 2:没有向左
@property (nonatomic, assign) int right; // 1:正在向右 2:没有向右

如上,每个int占用四个字节,总共占用4*4=16个字节,共16*8=128位,造成了大部分内存浪费,
而用联合体位域表现如下:

// 联合体
union {
    char bits; // 占用1位,8个字节,二进制表示为0b00000000
    // 位域
    struct { // 0000 1111
        char front  : 1; // 占用bits的第一个字节
        char back   : 1; // 占用bits的第二个字节
        char left   : 1; // 占用bits的第三个字节
        char right  : 1; // 占用bits的第四个字节
    };
} _direction;

#define LYDirectionFrontMask    (1 << 0)
#define LYDirectionBackMask     (1 << 1)
#define LYDirectionLeftMask     (1 << 2)
#define LYDirectionRightMask    (1 << 3)n

// 初始化
_direction.bits = 0b0000000000;

// 赋值
_direction.bits |= LYDirectionFrontMask;

// 取值
return _direction.front;

isa源码定义

union isa_t {
    isa_t() { }
    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_BITFIELD定义如下:
// arm64架构下
#   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

// x86_64架构
#   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采用了联合体的结构.arm64架构与x86_64架构主要区别在shiftclsextra_rc占用的长度.

isa存储内容

接下来我们一起看下isa中定义的各个字段具体指什么(arm64架构下)

  • nonpointer(存储在第0字节)是否为优化isa标志。0代表是优化前的isa,一个纯指向类或元类的指针;1表示优化后的isa,不止是一个指针,isa中包含类信息、对象的引用计数等。现在基本上都是优化后的isa

  • has_assoc(存储在第1个字节)关联对象标志位。对象含有或者曾经含有关联引用,0表示没有,1表示有,没有关联引用的可以更快地释放内存(dealloc的底层代码有体现)。

  • has_cxx_dtor(存储在第2个字节)析构函数标志位,如果有析构函数,则需进行析构逻辑,如果没有,则可以更快速地释放对象(dealloc的底层代码有体现)。

  • shiftcls(存储在第3-35字节)存储类的指针,其实就是优化之前isa指向的内容。在arm64架构中有33位用来存储类指针。x86_64架构有44位。

  • magic(存储在第36-41字节)判断对象是否初始化完成, 是调试器判断当前对象是真的对象还是没有初始化的空间。

  • weakly_referenced(存储在第42字节)对象被指向或者曾经指向一个ARC的弱变量,没有弱引用的对象可以更快释放(dealloc的底层代码有体现)。

  • deallocating(存储在第43字节)标志对象是否正在释放内存。

  • has_sidetable_rc(存储在第44字节)判断该对象的引用计数是否过大,如果过大则需要其他散列表来进行存储。

  • extra_rc(存储在第45-63字节。)存放该对象的引用计数值减1后的结果。对象的引用计数超过 1,会存在这个里面,如果引用计数为 10,extra_rc的值就为 9

由上我们可以知道shiftcls中存储了类的相关信息,接下来我们通过源码来验证下

// isa初始化
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;
    }
}

通过代码我们可以发现所谓的isa和对象进行关联就是将cls的信息存储在新的isashiftcls上,因为类信息的存储是从第四位开始的,所以需要将cls右移3位,即:newisa.shiftcls = (uintptr_t)cls >> 3;

我们再来看下newisa赋值前后的值分别是什么

// 赋值前,即执行newisa.shiftcls = (uintptr_t)cls >> 3;前
(lldb) p newisa
(isa_t) $4 = {
  cls = 0x001d800000000001
  bits = 8303511812964353
   = {
    nonpointer = 1
    has_assoc = 0
    has_cxx_dtor = 0
    shiftcls = 0
    magic = 59
    weakly_referenced = 0
    deallocating = 0
    has_sidetable_rc = 0
    extra_rc = 0
  }
}

// 赋值后
(lldb) p newisa
(isa_t) $5 = {
  cls = LYPerson
  bits = 8303516107940081
   = {
    nonpointer = 1
    has_assoc = 0
    has_cxx_dtor = 0
    shiftcls = 536871966
    magic = 59
    weakly_referenced = 0
    deallocating = 0
    has_sidetable_rc = 0
    extra_rc = 0
  }
} 

通过赋值前后的对比我们发现新的isacls变为了LYPerson,shiftcls也由0变成了536871966.由此也证明了isa与对象关联就是将类信息存入到isa的shiftcls

除此之外,我们还可以通过移位来证明,因为arm64shiftcls占用3~35共33位,所以我们可以通过先右移3位,再左移30位,最后再右移27位来获取shiftcls的值,如下:

(lldb) x/4gx p
0x10201f950: 0x001d8001000024dd 0x0000000000000000
0x10201f960: 0x0000000000000000 0x0000000000000000
(lldb) po 0x001d8001000024dd >> 3
1037939513492635

(lldb) po 1037939513492635 << 30
562951189692416

(lldb) po 562951189692416 >> 27
Person

(lldb)

当然,对于左移右移我们还可以通过位运算&来实现,如下

#   define ISA_MASK        0x0000000ffffffff8ULL
(lldb) po 0x001d8001000024dd & 0x0000000ffffffff8ULL
Person

至此,我们对isa的底层源码以及存储的值信息已经有了一定的了解,以后遇到关于isa的相关问题也能回答的更自信一些了.

你可能感兴趣的:(OC底层原理--isa结构分析)