注册
iOS

isa


如果对NSObject的实现有过研究,应该就知道,所有的对象,不管是实例对象,还是类对象,其实质都是一个C语言结构体

typedef struct objc_class *Class;
typedef struct objc_object *id;

struct objc_object {
private:
  isa_t isa;
...
};

struct objc_class : objc_object {
  // Class ISA;
  Class superclass;
  cache_t cache;             // formerly cache pointer and vtable
  class_data_bits_t bits;   // class_rw_t * plus custom rr/alloc flags
...
};

从以上结构体的定义,可以很清楚的看出,不管是实例对象还是类对象,其结构内的首个地址存储的就是isa指针,而isa指针的类型又为Class,即 struct objc_class,所以如果想对isa有一个更深入的认识,就需要对objc_class这个结构体进一步分析。

注:

struct objc_object {
private:
  isa_t isa;
...
};

这个结构体里面的第一个虽然不是struct objc_class指针,但是细看isa_t的定义,实际上它还是返回的一个struct objc_class的指针:

#include "isa.h"

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

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

这部分是apple对isa的优化。自从引入tagged pointer之后,isa就再也不是单纯的指针的,所以有时候直接查看对象内的isa时,会发现他根本就不是一个指针:
image.png
可以看出,取出对象c中第一个地址的值,是0x000001a1000f133d,显然它不符合一个指针,因为在arm64下,指针是8字节对齐的,也就是说,指针最后一位,要么是0x0,要么是0x8,还有就是,/*MACH_VM_MAX_ADDRESS 0x1000000000*/,但是显然0x000001a1000f133d0x1000000000大的多。
那么问题就来了:对于被优化的isa,我们该怎么去获取真正的isa指针呢?
要回答这个问题,我们要先了解下object_getClass(id _Nullable obj)的实现:

Class object_getClass(id obj)
{
  if (obj) return obj->getIsa();
  else return Nil;
}

inline Class
objc_object::getIsa()
{
  if (!isTaggedPointer()) return ISA();
...
}
//可以看出,最终的调用,指向`ISA()`函数,那么我们就去看看`ISA()`里面的操作。

runtime源码中找到ISA()

inline Class 
objc_object::ISA()
{
  assert(!isTaggedPointer());
#if SUPPORT_INDEXED_ISA
  if (isa.nonpointer) {
      uintptr_t slot = isa.indexcls;
      return classForIndex((unsigned)slot);
  }
  return (Class)isa.bits;
#else
  return (Class)(isa.bits & ISA_MASK);
#endif
}

对于arm64,可以简化为:

inline Class 
objc_object::ISA()
{
  return (Class)(isa.bits & ISA_MASK);
}

这个isa.bits的值,在上例中为:0x000001a1000f133d,而ISA_MASK的定义为:

#   define ISA_MASK        0x0000000ffffffff8ULL

仔细观察这个0x0000000ffffffff8ULL,你会发现,它恰好在第3bit到第35bit的值为1,而&上isa.bits的意思是取isa.bits的3bit~35bit的值,结合刚刚给出的isa_t的定义:

#   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

正好对上了shiftcls,似乎一切都是巧合,但其实不是,早在对象初始化的时候,就已经将isa指针的值,存入shiftcls了:

        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; //这里将cls的值往左移3bit的意思,为了节省内存,清空低三位不用的数,因为对于指针,低三位的值肯定是0。
其他bit的含义:
  • has_assoc
    对象含有或者曾经含有关联引用,没有关联引用的可以更快地释放内存

  • has_cxx_dtor
    当前对象有 C++ 或者 ObjC 的析构器(destructor),如果没有析构器就会快速释放内存。

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

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

  • deallocating
    对象正在释放内存

  • has_sidetable_rc
    对象的引用计数太大了,存不下

  • extra_rc
    对象的引用计数超过 1,会存在这个这个里面,如果引用计数为 10,extra_rc 的值就为 9

0 个评论

要回复文章请先登录注册