如果对NSObject
的实现有过研究,应该就知道,所有的对象,不管是实例对象,还是类对象,其实质都是一个C语言结构体
:
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
这个结构体进一步分析。
注:
struct objc_object {
private:
isa_t isa;
...
};
这个结构体里面的第一个虽然不是struct objc_class
指针,但是细看isa_t
的定义,实际上它还是返回的一个struct objc_class
的指针:
#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时,会发现他根本就不是一个指针:
可以看出,取出对象c中第一个地址的值,是
0x000001a1000f133d
,显然它不符合一个指针,因为在arm64下,指针是8字节对齐的,也就是说,指针最后一位,要么是0x0,要么是0x8,还有就是,/*MACH_VM_MAX_ADDRESS 0x1000000000*/
,但是显然0x000001a1000f133d
比0x1000000000
大的多。那么问题就来了:对于被优化的isa,我们该怎么去获取真正的isa指针呢?
要回答这个问题,我们要先了解下
object_getClass(id _Nullable obj)
的实现:
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()
:
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,可以简化为:
inline Class
objc_object::ISA()
{
return (Class)(isa.bits & ISA_MASK);
}
这个isa.bits
的值,在上例中为:0x000001a1000f133d,而ISA_MASK的定义为:
# define ISA_MASK 0x0000000ffffffff8ULL
仔细观察这个0x0000000ffffffff8ULL
,你会发现,它恰好在第3
bit到第35
bit的值为1,而&上isa.bits的意思是取isa.bits的3
bit~35
bit的值,结合刚刚给出的isa_t
的定义:
# 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
了:
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
网友评论