isa_t作用
在OC的类结构中出现的第一个成员变量就是联合体isa_t
struct objc_object {
private:
isa_t isa;
//在早期的版本中isa只是一个Class类型的指针
//Class _Nonnull isa;
public:
// ISA() assumes this is NOT a tagged pointer object
Class ISA();
...
}
在早期的32bit版本中isa就是一个单一的指针,用于存储当前对象的类或者类的元类. 但是在64bit为操作系统上,用一个8字节指针的长度只存储一个对象地址显然是浪费的(操作系统只有一部分地址是可用于存储对象地址的空间),所以apple对这个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
};
作为过渡也为了兼容早期的实现版本,这个结构中保存了变量Class class的实现,同时增加了uintptr_t(unsigned long)类型的变量bits,但由于使用的是联合体(公用体,共用变量空间),所以该结构只占用一个指针的空间.当使用bits变量进行存储时,利用位域结构将变量的各个位进行拆分赋予不同的含义,充分利用了内存空间.
利用位域使得变量内不仅仅保存了指针值,同时还保存了很多有用的信息.
# 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)
# 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
以主流的arm64为例,主要包含了:
- nonpointer:占用1bit,标识是否开启isa优化.如果是一个指针值该位为0,则表示当前结构的值只是一个指针没有保存其他信息;如果为1,则表示当前结构不是指针,而是一个包含了其他信息的位域结构;
- has_assoc:当前对象是否使用objc_setAssociatedObject动态绑定了额外的属性;
- has_cxx_dtor: 是否含有C++或者OC的析构函数,不包含析构函数时对象释放速度会更快;
- shiftcls: 这个值相当于早期实现中的isa指针,是真实的指针值,在arm64处理器上只占据33位,可见其实在内存中可以用来存储对象指针的空间是很有限的;
- magic:用于判断对象是否已经完成了初始化,在 arm64 中 0x16 是调试器判断当前对象是真的对象还是没有初始化的空间(在 x86_64 中该值为 0x3b);
- weakly_referenced:是否是弱引用对象;
deallocating
:对象是否正在执行析构函数(是否在释放内存);has_sidetable_rc
:判断是否需要用sidetable去处理引用计数;extra_rc
:存储该对象的引用计数值减一后的结果. 当对象的引用计数使用extra_rc足以存储时has_sidetable_rc=0;当对象的引用计数使用extra_rc不能存储时has_sidetable_rc=1.可见对象的引用计数主要存储在两个地方:如果isa中extra_rc足以存储则存储在isa的位域中;如果isa位域不足以存储,就会使用sidetable去存储.
isa_t初始化
inline void
objc_object::initIsa(Class cls, bool nonpointer, bool hasCxxDtor)
{
assert(!isTaggedPointer());
if (!nonpointer) {
isa.cls = cls;
} else {
assert(!DisableNonpointerIsa);
assert(!cls->instancesRequireRawIsa());
isa_t newisa(0);
/*
此时省略了SUPPORT_INDEXED_ISA预编译实现,这部分可以忽略,iOS中不使用这一技术
*/
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;
// This write must be performed in a single store in some cases
// (for example when realizing a class because other threads
// may simultaneously try to use the class).
// fixme use atomics here to guarantee single-store and to
// guarantee memory order w.r.t. the class index table
// ...but not too atomic because we don't want to hurt instantiation
isa = newisa;
}
}
在这一初始化过程中:
- newisa.bits = ISA_MAGIC_VALUE;首先使用ISA_MAGIC_VALUE将bits进行初始化,主要有两个作用:
- 将nonpointer标记为1,证明开启了isa优化;
- 初始化magic位,用于标记该对象已经初始化.
- newisa.has_cxx_dtor = hasCxxDtor;初始化对象标记是否有C++或者OC析构函数,如果存在在对象释放时需要消耗更多时间来对对象进行析构;
- newisa.shiftcls = (uintptr_t)cls >> 3;将对象指向的类或者类的元类指针赋值给位域shiftcls,因为前三位被nonpointer,has_assoc和has_cxx_dtor被占据,所以需要将真实的指针左移三位进行复制保存.
ISA_MASK
在iOS 64bit的操作系统中,为了屏蔽外界对于isa指针的直接获取对外部隐藏了真实的isa指针的值,而是需要使用ISA_MASK进行与操作来获取到真实的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
}
所以如果想要通过获取到对象真实的isa指针指向需要与ISA_MASK进行与操作:
# if __arm64__
# define ISA_MASK 0x0000000ffffffff8ULL
# elif __x86_64__
# define ISA_MASK 0x00007ffffffffff8ULL
# else
# error unknown architecture for packed isa
# endif
NSObject *obj = [[NSObject alloc] init];
void *ptr = (__bridge void *)obj;//墙纸转化为指针
void *isa = *(void **)ptr; //获取类中第一个成员变量isa_t值
isa = (void *)((uintptr_t)isa & ISA_MASK); //获取到isa真实的指针地址
id isaObj = (__bridge id)isa;//强转为id,即可获取到isa指向的真实对象
NSLog(@"isaObj == %@", isaObj);
该过程也可以通过LLDB实现:
(lldb) po (void *)obj
<NSObject: 0x10110f400>
(lldb) x/2g 0x10110f400
0x10110f400: 0x001d800100b38141 0x0000000000000000
(lldb) p/x (0x001d800100b38141 & ISA_MASK) //根据需要替换ISA_MASK
(unsigned long long) $1 = 0x0000000100b38140
(lldb) po $1
NSObject
ISA_MAGIC_MASK
从isa_t位域的定义中可以知道,magic变量占据了6bit的空间,使用ISA_MAGIC_MASK就可以快速地从isa_t中获取到magic值.
在__arm64__中,位域magic占据了从37位(1(nonpointer)+1(has_assoc)+1(has_cxx_dtor)+33(shiftcls) )开始的6bit空间,而ISA_MAGIC_MASK=0x000003f000000001ULL,除第37-42位和最低位外所有位均为0;在__x86_64__中,位域magic占据了从47位(1(nonpointer)+1(has_assoc)+1(has_cxx_dtor)+44(shiftcls))开始的6bit空间,而ISA_MAGIC_MASK=0x001f800000000001ULL,除第44-49位和最低位外其余位均为0.所以使用isa.bits & ISA_MAGIC_MASK,可以快速判断出是否开启nonpointer优化和magic的值.
# if __arm64__
# define ISA_MASK 0x0000000ffffffff8ULL
# define ISA_MAGIC_MASK 0x000003f000000001ULL
# define MAGIC_MASK 36
# elif __x86_64__
# define ISA_MASK 0x00007ffffffffff8ULL
# define ISA_MAGIC_MASK 0x001f800000000001ULL
# define MAGIC_MASK 47
# else
# error unknown architecture for packed isa
# endif
NSObject *obj = [[NSObject alloc] init];
void *ptr = (__bridge void *)obj;
ptr = *(void **)ptr;
uintptr_t magic = (uintptr_t)(void *)((uintptr_t)ptr & ISA_MAGIC_MASK);
if (magic & 1) {
NSLog(@"magic == %lu", magic >> MAGIC_MASK);
}