手撕iOS底层6 -- 对象isa深入分析

558 阅读3分钟

本章分析isa与类的关联

0x00 -- 联合体 & 位域

01联合体 union

unionc/c++里的自定义复合数据类型,与struct类似, 但是它们所占用的内存空间不一样。

参考我写的

联合体内存对齐

01 -- 对象指针地址初始化isa & 关联类信息

_class_createInstanceFromZone这个方法里下面👇的代码片段。

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

主要做了初始化isa_t ias指针,关联类信息

inline void 
objc_object::initIsa(Class cls, bool nonpointer, bool hasCxxDtor) 
{ 
    ASSERT(!isTaggedPointer()); 
    
    if (!nonpointer) {
        // 不是 nonpointer
        isa = isa_t((uintptr_t)cls);
    } else {
        // 是 nonpointer(不是一个简单的指针) 被优化过的isa指针
        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;
    }
}

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
};
  1. 首先判断类是不是nonpointer,自己创建的类都是nonpointer, 系统内建的类绝大部分是非nonpointer
  2. 初始化一个isa_t union变量newisa, 使用联合体的初始化方法,成员变量都初始化为0

  1. 走到宏定义SUPPORT_INDEXED_ISAelse分支里,把ISA_MAGIC_VALUE给到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)

# else
#   error unknown architecture for packed isa
# endif

如上代码片段所示, 分为俩个架构arm64x86_64,当前运行环境为mac端,所以走x86_64的逻辑。

1. nonpointer 表示自定义的类,占1位
  • 0 纯isa
  • 1 是nonpointer, 不只是只有类对象地址,还包括了其它信息,如类信息引用计数等。
2. has_assoc 表示关联对象标识,占1位
  • 0 没有关联对象
  • 1 有关联对象
3. has_assoc 表示该对象是否有c++/oc析构函数, 占1位
  • 如果有析构函数, 需要做析构逻辑
  • 没有,则可以更快释放对象
4.shiftcls,存储类地址,即类信息
  • arm64架构占33位
  • 其它占44位
5. magic 用于调试器判断当前对象是真的对象还是没有初始化空间,占6位
6. weakly_referenced 表示当前对象是否被指向或者曾经指向一个ARC弱变量。
7.deallocating 表示当前对象是否正在释放内存
8. has_sidetable_rc 表示当前对象引用计数大于0的时候,需要借用该变量存储进位
9. extra_rc 表示当前对象的引用计数值,实际上是引用计数值减1,例如引用计数为10, 那么这个extra_rc为9。如果大于10,需要用到has_sidetable_rc


0x02 -- 进一步探索isa的初始化以及绑定cls

objc_object::initIsa中,

isa_t newisa(0);执行完, 通过LLDB调试看一下当前内存布局

过了当前断点, 通过union的构造函数isa_t(uintptr_t value) : bits(value) { }使其各个位置都为0

#   define ISA_MAGIC_VALUE 0x000001a000000001ULL // arm64 
#   define ISA_MAGIC_VALUE 0x001d800000000001ULL // x86_64 
newisa.bits = ISA_MAGIC_VALUE;

magicx86_64下占6位, 是4752位,

通过这俩张图所占位数与计算, magic的值是59.

接着把newisa.shiftcls = (uintptr_t)cls >> 3;赋值

shiftcls存储着它的类对象的指针。这俩是关联类的关键。

因为newisa要存储cls的值 ,cls也有isa,所以要把低三位左移抹掉, 防止赋值的时候把cls的高三位就不能正确的存储到shiftcls中。

通过打印cls左移3位得出值与newisashiftcls存储的值一毛一样。

最后isa = newisa;newisa返回给对象isa,这里就把cls关联到对象isa


0x03 -- 通过isa获取类

对位运算不熟悉,可以参考的我的学会位运算,助力开发高性能
  • 通过拿isa掩码位与来获取
  • 通过左移和右移得到shiftcls来获取
  • 通过runtime api的验证
  1. 方式一

得出类对象

  1. 方式二

左移和右移也能将shiftcls得到, 把低三位和高17的信息抹掉,只留下中间的信息。就是class的地址。

  1. 方式三

通过object_getClass获取类信息

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

inline Class 
objc_object::getIsa() 
{
    if (fastpath(!isTaggedPointer())) return ISA();

    extern objc_class OBJC_CLASS_$___NSUnrecognizedTaggedPointer;
    uintptr_t slot, ptr = (uintptr_t)this;
    Class cls;

    slot = (ptr >> _OBJC_TAG_SLOT_SHIFT) & _OBJC_TAG_SLOT_MASK;
    cls = objc_tag_classes[slot];
    if (slowpath(cls == (Class)&OBJC_CLASS_$___NSUnrecognizedTaggedPointer)) {
        slot = (ptr >> _OBJC_TAG_EXT_SLOT_SHIFT) & _OBJC_TAG_EXT_SLOT_MASK;
        cls = objc_tag_ext_classes[slot];
    }
    return cls;
}

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); // 也是拿bits去和掩码做位与运算
#endif
}

欢迎大佬留言指正😄,码字不易,觉得好给个赞👍 有任何表达或者理解失误请留言交流;共同进步;