本文基于可编译的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类似,都是对指针空间的最大化利用)。
回到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_MASK 0x0000000ffffffff8ULL
# define ISA_MAGIC_MASK 0x000003f000000001ULL
# 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_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
#if SUPPORT_INDEXED_ISA
# if __ARM_ARCH_7K__ >= 2 || (__arm64__ && !__LP64__)
// armv7k or arm64_32
# define ISA_INDEX_IS_NPI_BIT 0
# define ISA_INDEX_IS_NPI_MASK 0x00000001
# define ISA_INDEX_MASK 0x0001FFFC
# define ISA_INDEX_SHIFT 2
# define ISA_INDEX_BITS 15
# define ISA_INDEX_COUNT (1 << ISA_INDEX_BITS)
# define ISA_INDEX_MAGIC_MASK 0x001E0001
# 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。
总结:
- 获取对象的Class,需要通过getIsa()接口
- 首先需要判断它是不是普通pointer还是Tagged pointer,因为Tagged pointer的Class信息存放在指针本身,它并不实际指向一个objc_object变量(对象)。
- 接着判断isa是不是non-pointer,普通指针指向的就是Class,non-pointer的话通过位运算的取出Class。