第二节—关联了对象和类的isa

本文为L_Ares个人写作,包括图片皆为个人亲自操作,以任何形式转载请表明原文出处。

前一节接触到了isa,相信做iOS开发的大家对isa听到的太多了,尤其是某张非常出名的runtime的流程图里面。isa可谓是贯穿了我们的开发之路。

那么根据第一节中我们没有详细说到的isa在对象alloc中的初始化和绑定类与对象,开始探索isa

还是要使用到objc的部分开放源码。

一、关于allocisa的初始化InitIsa

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

这段代码是我们之前在将allocobjcls绑定的时候说到的,当时说了obj是通过isa和类进行的绑定,从而确定了obj的类型,那么这个isa怎么来的?isa又包含了什么内容在里面?

这需要进入到initInstanceIsa

inline void 
objc_object::initInstanceIsa(Class cls, bool hasCxxDtor)
{
    ASSERT(!cls->instancesRequireRawIsa());
    ASSERT(hasCxxDtor == cls->hasCxxDtor());

    initIsa(cls, true, hasCxxDtor);
}

上面的ASSERT不要管,主要的是那句initIsa,直接进入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

nonpointer是什么?

nonpointer : 表示是否对isa开启指针优化 。0代表是纯isa指针,1代表除了地址外,还包含了类的一些信息、对象的引用计数等。

那么我们从上面传来的是true,所以!nonpointer是不会走的。直接看else

isa_t newisa(0);

第二节中说过了,isa_t这个是isa的类,是个联合体。所以这里就是初始化了isa

然后看到下面一大堆的给isa对象newisa进行set,那就看一下,这都是些什么元素。

直接进入isa_t

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

这里就找到了一个bits,其他的呢?全都在下面的那个结构体的元素里面,直接进入这个ISA_BITFIELD元素。

# 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

// SUPPORT_PACKED_ISA
#endif

这里就发现了isa带有的元素,并且都是用位域来指定的大小。

我们因为用的是Mac模拟器,所以进入的肯定是__x86_64__,手机才是进入__arm64__,这个就不多说了,大家应该都是清楚的。两个宏定义的元素是一样的,只不过不同的系统下,存在一点内存大小的差异。

那么看一下__x86_64__(因为这个我们能调试,真机不好运行)objc781的源码中,这些元素都是什么意思,nonpointer说过了,就不再废话。

  • has_assoc : 关联对象标志位,占用1位内存空间,表明是否有关联对象。
    0则没有关联对象。
    1则有关联对象。

  • has_cxx_dtor : 该对象是否有C++或Objc的析构器,占1位内存空间,
    1表示有析构函数,需要做一些析构的逻辑处理。
    0表示没有析构函数,可以更快的释放对象。

  • shiftcls : 存储类的指针的值,也就是类的地址,类的信息。在开启指针优化的前提下,也就是nonpointer1的情况下。
    arm64 : iOS系统,占用33位内存空间来存储类的指针。
    x86_64 : MacOS系统,占用44位内存空间存储类的指针。

  • magic : 用于给调试器判断,占6位内存空间。判断当前对象是真的对象还是一段没有初始化的空间。

  • weakly_refrenced : 是否被指向或者曾经指向一个ARC的弱变量,占1位内存空间。
    1表示指向或者曾经指向过
    0表示没有指向,没有弱饮用的对象释放的更快速。

  • deallocating : 对象是否正在释放,占1位内存空间。
    1表示对象正在释放
    0表示对象不是正在释放的过程中。

  • has_sidetable_rc : 是否需要sidetable(runtime的散列表)来存储引用计数,占1位内存空间。
    1表示需要,那么这个时候对象的引用计数应该大于了10。
    0表示不用。

  • extra_rc : 表示该对象的引用计数值。占用8位内存空间,这就很明显了,占用8位内存空间的除了char*就那几个数值型的类型用的最多。

了解了上述这些isa这个联合体对象带有的元素后,再看这一句

newisa.shiftcls = (uintptr_t)cls >> 3;

这就很明显了,它就是把clsobj关联到一起的“元凶”。

二、验证isa把clsobj关联

上面说是这么说,但是这都是根据源码含义的猜测,能不能拿出来点实际的证据证明,就是isaclsobj关联了呢?介绍两种方法。

1. 通过shiftcls

shiftcls上面介绍过了,说是存储类的地址的,存储类的信息的。那么就看看它和我们初始化的对象的isa地址对不对的上了,如果对的上,那就证明初始化的对象的isa的确是指向了类。

首先在main.m中为JDMan类初始化一个对象,打印它的内存地址。

图片.png

我们说无论编译器优化怎么优化这个对象的属性的内存,无论它的属性内存发生了什么变化,这个对象的第一个对象一定是isa。为什么这么说?因为这个的对象的isa是来自于继承,根本还没有编译当前的属性列表,在你的属性列表形成之前,人家isa就已经在那里了。

继续,

  1. 即然已经拿到isa的内存地址了,那么打印成二进制看看,一会儿正好可以和shiftcls保存的类的信息做一下对比。所以打印它的二进制地址。
图片.png
  1. 然后我们拿到我们目标对象,我们的目标对象不就是cls吗,那就拿类的地址也放着。
图片.png
  1. 然后我们看一下shiftcls
newisa.shiftcls = (uintptr_t)cls >> 3;

这里发现,shiftcls的类型明明是uintptr_t,那我们就把目标类也变成这个

图片.png

那现在JDMan的地址也是uintptr_t类型的了。

  1. newisa.shiftcls = (uintptr_t)cls >> 3;表明了,类的地址,右移3位才存到shiftcls,那就右移3位。
图片.png

好了,现在类已经满足存进去的要求了,存进去以后,地址就应该是$4所示。

  1. 类的计算完成了,那我们开始拿shiftclsshiftcls是在isa里面的,上面已经说过了,所以进去看一下。

    图片.png

    shiftcls的前面有3位不是它的吧。因为iOS是小端啊,所以应该右面才是头,那就先右移3位,把不是shiftcls的内容先去掉。
    图片.png

  2. 头上的3位不是shiftcls的内容去掉了,尾巴上还有17位不是shiftcls的内容,而且刚才右移了3位,是不是应该把人家那3位找补回来才能对齐,所以这次左移17+3=20位

图片.png

现在就只剩下shiftcls的内容了,和刚才的JDMan的内存做对比,是不是一样的?

那是不是就证明了isa存储的shiftcls就是我们的类。

2. 通过object_getClass验证isa关联了对象和类

我相信下面的这个runtime中的函数,大家非常的熟悉。

object_getClass(对象);

我们通过一个对象找到这个对象的类,那么这是怎么找到的?还是通过isa找到的。下面继续验证的确就是通过isa找到的。

  1. 首先,从最直接的入手,就直接看object_getClass,即然说isa关联了,那么源码里面必有体现。
Class object_getClass(id obj)
{
    if (obj) return obj->getIsa();
    else return Nil;
}
  1. object_getClass的源码中非常简单的一句,如果有obj参数传进来,那么就返回obj->getIsa(),那么就继续看getIsa()里面又是什么。
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;
}
  1. 代码倒是非常的多,但是我们确定的是它返回的是isa,所以只看第一行就行了,因为就它是isa。那么再进入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
}

这里走的是带掩码的return (Class)(isa.bits & ISA_MASK);

这里说下这个掩码是个什么,看英文:MASK,就是面具,面具有什么特性呢?就是脸上只能看到你的鼻子眼睛和嘴,别的地方你看不到。就是说,我想让你看到的地方你才可以看,不想让你看到的地方,你就看不到,那么就可以使用这个MASK来控制可以读取的地方。

  1. 这是很明显的拿isa的内存做了位运算,那么看一下这个ISA_MASK多少:
# elif __x86_64__
#   define ISA_MASK        0x00007ffffffffff8ULL

因为是用Mac模拟器,所以肯定用x86的。

  1. 那我们拿我们的目标——JDMan的内存,这次用lldb打印16进制的内存。就不转换成2进制了。
图片.png
  1. 然后我们继续拿isa的内存来看,根据return (Class)(isa.bits & ISA_MASK);这句的要求,把isa的内存和ISA_MASK做与的位运算。
图片.png

于是又被证明了isa关联了对象与类,这个更直观。

三、结论

类和对象之间的关联,的确就是由isa来完成的,对象中的isa也是继承与类的isa的。

你可能感兴趣的:(第二节—关联了对象和类的isa)