isa

如果对NSObject的实现有过研究,应该就知道,所有的对象,不管是实例对象,还是类对象,其实质都是一个C语言结构体

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
typedef struct objc_class *Class;
typedef struct objc_object *id;

struct objc_object {
private:
isa_t isa;
...
};

struct objc_class : objc_object {
// Class ISA;
Class superclass;
cache_t cache; // formerly cache pointer and vtable
class_data_bits_t bits; // class_rw_t * plus custom rr/alloc flags
...
};

从以上结构体的定义,可以很清楚的看出,不管是实例对象还是类对象,其结构内的首个地址存储的就是isa指针,而isa指针的类型又为Class,即 struct objc_class,所以如果想对isa有一个更深入的认识,就需要对objc_class这个结构体进一步分析。

注:

1
2
3
4
5
struct objc_object {
private:
isa_t isa;
...
};

这个结构体里面的第一个虽然不是struct objc_class指针,但是细看isa_t的定义,实际上它还是返回的一个struct objc_class的指针:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#include "isa.h"

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

# 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)

这部分是apple对isa的优化。自从引入tagged pointer之后,isa就再也不是单纯的指针的,所以有时候直接查看对象内的isa时,会发现他根本就不是一个指针:
image.png
可以看出,取出对象c中第一个地址的值,是0x000001a1000f133d,显然它不符合一个指针,因为在arm64下,指针是8字节对齐的,也就是说,指针最后一位,要么是0x0,要么是0x8,还有就是,/*MACH_VM_MAX_ADDRESS 0x1000000000*/ ,但是显然0x000001a1000f133d0x1000000000大的多。
那么问题就来了:对于被优化的isa,我们该怎么去获取真正的isa指针呢?
要回答这个问题,我们要先了解下object_getClass(id _Nullable obj)的实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
Class object_getClass(id obj)
{
if (obj) return obj->getIsa();
else return Nil;
}

inline Class
objc_object::getIsa()
{
if (!isTaggedPointer()) return ISA();
...
}
//可以看出,最终的调用,指向`ISA()`函数,那么我们就去看看`ISA()`里面的操作。

runtime源码中找到ISA()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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
}

对于arm64,可以简化为:

1
2
3
4
5
inline Class 
objc_object::ISA()
{
return (Class)(isa.bits & ISA_MASK);
}

这个isa.bits的值,在上例中为:0x000001a1000f133d,而ISA_MASK的定义为:

1
#   define ISA_MASK        0x0000000ffffffff8ULL

仔细观察这个0x0000000ffffffff8ULL,你会发现,它恰好在第3bit到第35bit的值为1,而&上isa.bits的意思是取isa.bits的3bit~`35bit的值,结合刚刚给出的isa_t`的定义:

1
2
3
4
5
6
7
8
9
10
#   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

正好对上了shiftcls,似乎一切都是巧合,但其实不是,早在对象初始化的时候,就已经将isa指针的值,存入shiftcls了:

1
2
3
4
5
6
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; //这里将cls的值往左移3bit的意思,为了节省内存,清空低三位不用的数,因为对于指针,低三位的值肯定是0。

其他bit的含义:
  • has_assoc
    对象含有或者曾经含有关联引用,没有关联引用的可以更快地释放内存
  • has_cxx_dtor
    当前对象有 C++ 或者 ObjC 的析构器(destructor),如果没有析构器就会快速释放内存。
  • magic
    用于调试器判断当前对象是真的对象还是没有初始化的空间
  • weakly_referenced
    对象被指向或者曾经指向一个 ARC 的弱变量,没有弱引用的对象可以更快释放
  • deallocating
    对象正在释放内存
  • has_sidetable_rc
    对象的引用计数太大了,存不下
  • extra_rc
    对象的引用计数超过 1,会存在这个这个里面,如果引用计数为 10,extra_rc 的值就为 9