oc 获取对象 class 地址

在看 objc-msg-arm64.s 的源码时,从对象中获取其 class 地址的方式有些没看明白。它是通过将对象地址和 0xffffffff8 进行与运算来获取。如下所示,其中 $0self 的地址。

// ISA_MASK = 0x0000000ffffffff8ULL
// p16 & 0x0000000ffffffff8ULL,获取真正的类地址
and p16, $0, #ISA_MASK

在查看了 objc-runtime 的源码之后,弄懂了其中的原理。因为在最新 objc_object 的定义中,isa 不再是 Class,而是 isa_t 联合体类型。

isa 布局

下面我们来看一下 isa 的布局。isa 是 isa_t 类型,它是个联合体。

struct objc_object {
private:
    isa_t 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 的定义,在 isa.h 中,这里是获取 class 的关键。它主要定义了一些位域来存储不同的信息。

#   define ISA_MASK        0x0000000ffffffff8ULL
#   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

重点关注第 4 项 shiftcls,它占 33 位,存放的是 cls 的地址。ISA_MASK,这个掩码就是用来取出中间的 33 位。

不知道大家有没有跟我一样的疑问?虽然照这样是取出了 33 位,可末尾还有 3 位是 0。那么按照常规思路,不是应该右移 3 位将其去除吗?

但事实上这样计算是没错的。因为 shiftcls 在赋值时,就将地址右移了 3 位。由于是 8 字节对齐,最后 3 位肯定为 0,右移之后也无影响,到时再补上 0 即可。不过也看到其他文章说这样做是为了节省空间。所以按照上述方式取出,正好是原始地址。

到底对不对,我们看下源码就知道了。从下面代码中可以看到,最后一行,的确是将 cls 右移了 3 位。

inline void 
objc_object::initIsa(Class cls, bool nonpointer, bool hasCxxDtor) {

      // 省略部分代码
      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;

        // 这里已经右移了 3 位
    newisa.shiftcls = (uintptr_t)cls >> 3;
}

实例验证

下面我们通过以下实例验证一下,每句都添加了详细的注释。

NSObject *obj = [NSObject new];
    
// p 的值是 obj 的地址
void *p = (__bridge void *)(obj);

// 由于 isa 是 struct objc_object 的第一个元素,那么将其转换为 long 64 位,对 p1 进行解引用后,则可得到 isa 的地址。
long *p1 = p;

// 对 p1 进行解引用后,得到 isa 的地址。
long isaAdress = *p1;

// 在 arm64 上,isa 不再是类的指针,而是联合体。需通过 isa & 0xffffffff8 获取类地址
// 也可以通过 lldb,输入 p/x obj->isa, 可查看其 class 地址。
long classAdress = isaAdress & 0xffffffff8;

// object_getClass(obj) 得到 obj 对应的类地址
NSLog(@"obj address:%p\n use object_getClass to get class address:%p\n use pinter to get obj address:0x%lx\n union isa address:0x%lx\n use isa address and 0xffffffff8 to get class address:0x%lx\n", obj, object_getClass(obj), (long)p, isaAdress, classAdress);

主要思路是,通过解引用 obj 地址,获取 isa 的值。然后进行与运算,得到 class 地址。

demo 中的实例 obj 实际上是指向 objc_object 的指针,而 objc_object 的第一个成员是 isa,那么也就是说 isa 自身的存储地址也就是 obj 的地址。而要想得到 isa 的内容,需要解引用该地址。

打印结果:

obj address:0x280394210
use object_getClass to get class address:0x1f0d9af08
use pinter to get obj address:0x280394210
union isa address:0x1000001f0d9af09
use isa address and 0xffffffff8 to get class address:0x1f0d9af08

从结果中可以看到,isaAdress & 0xffffffff8 的值跟通过 object_getClass 获取到的值相等,都为 0x1f0d9af08。这样也就验证了上述计算方式的正确性。

另外,也可在 lldb 中输入 p/x obj->isa,查看 class 的地址。

你可能感兴趣的:(oc 获取对象 class 地址)