本文依据可编译的objc4-750版别源码进行评论,文章中旦有错误之处,期望大家不吝指正。 假如想要获取最新源码:objc最新源码在这里

前语

在探求OC runtime 目标和类的内部完成中(位于objc-private.h),isa是十分要害的成员,runtime中目标便是经过isa来获取所属的Class。其实,早期的isa便是指向Class的指针,可是现在,它已经不是简单的指向目标的指针了。

getIsa()

从runtime源码中,能够看到isa是一个私有成员,你不能直接经过拜访isa成员来获取Class信息了,有必要经过runtime中提供的相应的接口getIsa():

struct objc_object {
private:
isa_t isa;
public:
// ISA() assumes this is NOT a tagged pointer object
Class ISA();
// getIsa() allows this to be a tagged pointer object
Class getIsa();
  // ...
}

那么如何经过getIsa()接口获取Class呢?来到getIsa()方法完成(objc-object.h中):

inline Class
objc_object::getIsa()
{
// 假如this是 tagged pointer,目标信息存在了this指针自身的内存空间
// 不然经过ISA()方法从this->isa 成员中解析
if (!isTaggedPointer()) return ISA();
// 从tagged pointer中解析出Class type index,然后从类型索引表中查找对应的Class
uintptr_t ptr = (uintptr_t)this;
if (isExtTaggedPointer()) { // 扩展的tagged pointer
uintptr_t slot = (ptr >> _OBJC_TAG_EXT_SLOT_SHIFT) & _OBJC_TAG_EXT_SLOT_MASK;
return objc_tag_ext_classes[slot];
} else { // 一般的tagged pointer
uintptr_t slot =(ptr >> _OBJC_TAG_SLOT_SHIFT) & _OBJC_TAG_SLOT_MASK;
return objc_tag_classes[slot];
}
}

getIsa()方法中,会先判别this指针是否是tagged pointer,假如是的话,Class信息直接寄存在指针本身的内存空间中,所以从指针中解析获取Class。关于Tagged pointer,能够看之前的这篇《Objective-C Tagged Pointer》

this为非 tagged pointer的话,才经过ISA()方法(objc-object.h中),从isa私有成员中获得Class,但是ISA()方法中,也不是直接回来this->isa指向的目标的:

inline Class
objc_object::ISA()
{
assert(!isTaggedPointer());
#if SUPPORT_INDEXED_ISA // 目前主要是watch设备支持Indexed isa
if (isa.nonpointer) { 
uintptr_t slot = isa.indexcls;
return classForIndex((unsigned)slot);
}
return (Class)isa.bits;// 一般指针
#else
/*
  以下这行等同于:
  if (isa.nonpointer) { // nonpointer格局,经过按位与ISA_MASK来获取成果
    return (Class)(isa.bits & ISA_MASK);
  } else { // 一般指针
    return (Class)isa.bits;
  }
  Packed non-pointer 和一般的pointer寄存地址的位域相同,因此两种都可经过按位与ISA_MASK来获取成果
  */
return (Class)(isa.bits & ISA_MASK);
#endif
}

这边触及到了non-pointer,什么是non-pointer?

Non-Pointer

一个指针(地址)需求用64位也便是16-byte的内存空间来存储,但实践上往往用不到悉数,只用到了中间的部分,高位16-bit用不到,由于内存按字节对齐的要求,所以低位值也总是为0。因此,将指针中除了寄存地址之外的位运用起来寄存额定的信息,这种非朴实的指针便是non-pointer(感觉跟tagged pointer类似,都是对指针空间的最大化运用)。

Objective-C runtime源码小记-再谈isa

回到runtime源码,isa的类型是isa_t, 继续查看isa_t的界说(objc-private.h中):

// 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_t是一个共用体,它本质上是占用了一个指针的内存空间,程序中它既能够是一个Class,也能够是一个叫bits的指针,或者是用ISA_BITFIELD宏界说的位域结构体。

查看(isa.h)中关于 ISA_BITFIELD宏的界说,能够了解non-pointer isa 的具体是如何存储的:

#if SUPPORT_PACKED_ISA
// extra_rc must be the MSB-most field (so it matches carry/overflow flags)
// nonpointer must be the LSB (fixme or get rid of it)
// shiftcls must occupy the same bits that a real class pointer would
// bits + RC_ONE is equivalent to extra_rc + 1
// RC_HALF is the high bit of extra_rc (i.e. half of its range)
// future expansion:
// uintptr_t fast_rr : 1;// no r/r overrides
// uintptr_t lock : 2;// lock for atomic property, @synch
// uintptr_t extraBytes : 1;// allocated with extra bytes
# if __arm64__
#define ISA_MASK0x0000000ffffffff8ULL
#define ISA_MAGIC_MASK0x000003f000000001ULL
#define ISA_MAGIC_VALUE 0x000001a000000001ULL
#define ISA_BITFIELD\
uintptr_t nonpointer: 1;/*标记是否为一般指针,0表明一般指针,1表明non-pointer指针,每一位存储各种数据*/ \
uintptr_t has_assoc: 1;/*表明该目标是否包括 associated object,假如没有,则析构时会更快*/ \
uintptr_t has_cxx_dtor: 1;/*表明该目标是否有 C++ 或 ARC 的析构函数,假如没有,则析构时更快*/ \
uintptr_t shiftcls: 33; /*MACH_VM_MAX_ADDRESS 0x1000000000 类的指针*/ \
uintptr_t magic: 6;/*固定值为 0xd2,用于在调试时分辩目标是否未完成初始化*/ \
uintptr_t weakly_referenced : 1;/*表明该目标是否有过 weak 目标,假如没有,则析构时更快*/ \
uintptr_t deallocating: 1;/*表明该目标是否正在析构*/ \
uintptr_t has_sidetable_rc: 1;/*表明该目标的引证计数值是否过大无法存储在 isa 指针*/ \
uintptr_t extra_rc: 19/*存储引证计数值(实践引证计数值减一后的成果)*/
#define RC_ONE(1ULL<<45)
#define RC_HALF(1ULL<<18)
# elif __x86_64__
#define ISA_MASK0x00007ffffffffff8ULL
#define ISA_MAGIC_MASK0x001f800000000001ULL
#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
#if SUPPORT_INDEXED_ISA
# if__ARM_ARCH_7K__ >= 2||(__arm64__ && !__LP64__)
// armv7k or arm64_32
#define ISA_INDEX_IS_NPI_BIT0
#define ISA_INDEX_IS_NPI_MASK 0x00000001
#define ISA_INDEX_MASK0x0001FFFC
#define ISA_INDEX_SHIFT2
#define ISA_INDEX_BITS15
#define ISA_INDEX_COUNT(1 << ISA_INDEX_BITS)
#define ISA_INDEX_MAGIC_MASK0x001E0001
#define ISA_INDEX_MAGIC_VALUE 0x001C0001
#define ISA_BITFIELD\
uintptr_t nonpointer: 1;\
uintptr_t has_assoc: 1;\
uintptr_t indexcls: 15;\
uintptr_t magic: 4;\
uintptr_t has_cxx_dtor: 1;\
uintptr_t weakly_referenced : 1;\
uintptr_t deallocating: 1;\
uintptr_t has_sidetable_rc: 1;\
uintptr_t extra_rc: 7
#define RC_ONE(1ULL<<25)
#define RC_HALF(1ULL<<6)
# else
#error unknown architecture for indexed isa
# endif
// SUPPORT_INDEXED_ISA
#endif

具体来说,non-pointer isa依据CPU架构不同会有几种不同的存储方法,但主要分为两大类:

  • Packed Isa – 寄存类指针的位域(shiftcls)和一般的isa指针相同,数据寄存在其他位域,因此经过和ISA_MASK做按位与运算就能取得实践的指针地址。

  • Indexed Isa – 这个是运行在watch设备上的,由于watch设备ABI的指针只要32位,没有满足的空间来像Packed isa相同寄存除了地址之外的其他数据,因此它不是直接寄存指针地址,而是在位域(indexcls)中寄存了一个class索引表(一个array)的index值,Class第一次加载到内存中的时候添加到class索引表, 假如索引表元素超越容量,则不再运用Indexed isa格局,回退到一般的isa。

总结:

  1. 获取目标的Class,需求经过getIsa()接口
  2. 首先需求判别它是不是一般pointer仍是Tagged pointer,由于Tagged pointer的Class信息寄存在指针本身,它并不实践指向一个objc_object变量(目标)。
  3. 接着判别isa是不是non-pointer,一般指针指向的便是Class,non-pointer的话经过位运算的取出Class。

Objective-C runtime源码小记-再谈isa