通过 OC底层探索 - _class_createInstanceFromZone 一章,我们知道 isa
是 isa_t
类型的。
union isa_t {
isa_t() { }
isa_t(uintptr_t value) : bits(value) { }
uintptr_t bits;
private:
// Accessing the class requires custom ptrauth operations, so
// force clients to go through setClass/getClass by making this
// private.
Class cls;
public:
#if defined(ISA_BITFIELD)
struct {
ISA_BITFIELD; // defined in isa.h
};
bool isDeallocating() {
return extra_rc == 0 && has_sidetable_rc == 0;
}
void setDeallocating() {
extra_rc = 0;
has_sidetable_rc = 0;
}
#endif
void setClass(Class cls, objc_object *obj);
Class getClass(bool authenticated);
Class getDecodedClass(bool authenticated);
};
可见,isa
实际上是个联合体。对底层有所了解的话,都会知道,isa
是用来存放 Class类型的结构体指针的。那么为什么 isa
会是一个联合体呢?
这是因为isa
分为 POINTER_ISA
(指针类型)和 NONPOINTER_ISA
(非指针类型)。
相对早期版本,苹果现在采用了 nonPointerIsa
来进行内存优化。因为 isa
是一个Class类型的结构体指针, 占8个字节,但是8个字节意味着它就有 8*8=64
位。用来存储一个指针根本不需要这么多内存空间,会造成浪费。而 nonPointerIsa
会把一些和对象息息相关的信息也放置在这块内存中。
所以为了兼容早期版本的设计,采用了联合体的设计。
那么 nonPointerIsa
中到底存了些什么呢?
通过 ISA_BITFIELD
这个宏可以看到,在不同架构上,存储的内容是不同的。
// arm64 (模拟器)
uintptr_t nonpointer : 1;
uintptr_t has_assoc : 1;
uintptr_t weakly_referenced : 1;
uintptr_t shiftcls_and_sig : 52;
uintptr_t has_sidetable_rc : 1;
uintptr_t extra_rc : 8
// arm64 (真机)
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 unused : 1;
uintptr_t has_sidetable_rc : 1;
uintptr_t extra_rc : 19
// x86_64
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 unused : 1;
uintptr_t has_sidetable_rc : 1;
uintptr_t extra_rc : 8
uintptr_t nonpointer : 1;
这种写法,是使用了位域。
位域的宽度不能超过前面数据类型的最大⻓度,比如int占4个字节也就是32位,那后面的数字就不能超过32。一个位域存储在同一个字节中,如一个字节所剩空间不够存放另一位域时,则会从下一单元起存放该位域。位域能够节省一定的内存空间。
以上面 x86_64
为例,isa
一共有 64 位,第1位存放 nonpointer
的值,第2位存放 has_assoc
的值,第 4 到 47 位存放的是 shiftcls
的值, 第 57 到 64 位存放 extra_rc
的值。
nonpointer_isa存储内容含义
nonpointer
: 是否对 isa 指针开启指针优化 0 - 未开启,当前是纯 isa指针; 1 - 开启优化,isa中不止存储了指针,还存储其他信息
has_assoc
: 关联对象标志位, 0 - 没有,1 - 存在
has_cxx_dtor
: 该对象是否有 C++ 或者 Objc 的析构器,如果有析构函数,则需要做析构逻辑,如果没有,则可以更快的释放对象
shiftcls
: 存储类指针的值。开启指针优化的情况下,在 x86_64 架构中,有 44 位用来存储类指针。
magic
: 用于调试器判断当前对象是真的对象还是没有初始化的空间
weakly_referenced
: 标志对象是否被指向或曾经指向一个ARC的弱变量,没有弱引用的对象可以更快释放
unused
: 标志对象是否正在释放
has_sidetable_rc
: 是否需要使用 sidetable 来存储引用计数
extra_rc
: 对象的引用计数值
通过 isa
获取类地址
if (NSHostByteOrder() == NS_BigEndian) {
NSLog(@"大端模式");
}else if (NSHostByteOrder() == NS_LittleEndian) {
NSLog(@"小端模式");
}else {
NSLog(@"NS_UnknownByteOrder");
}
TestClass *test = [TestClass new];
因为测试使用的是 iPhone7 真机,所以是arm64架构的,又通过打印证明是小端模式
根据nonpointer_isa的结构可以得知,除去左侧28位,和右侧3位,后中间的33位正是存储的类地址。
我们通过 isa
的位运算的到的结果 与 TestClass.class
对比,发现的确中间33位存放的就是类地址。
isa_t
里面提供了获取类的方法
Class getClass(bool authenticated);
inline Class
isa_t::getClass(MAYBE_UNUSED_AUTHENTICATED_PARAM bool authenticated) {
#if SUPPORT_INDEXED_ISA
return cls;
#else
uintptr_t clsbits = bits;
# if __has_feature(ptrauth_calls)
# if ISA_SIGNING_AUTH_MODE == ISA_SIGNING_AUTH
// Most callers aren't security critical, so skip the
// authentication unless they ask for it. Message sending and
// cache filling are protected by the auth code in msgSend.
if (authenticated) {
// Mask off all bits besides the class pointer and signature.
clsbits &= ISA_MASK;
if (clsbits == 0)
return Nil;
clsbits = (uintptr_t)ptrauth_auth_data((void *)clsbits, ISA_SIGNING_KEY, ptrauth_blend_discriminator(this, ISA_SIGNING_DISCRIMINATOR));
} else {
// If not authenticating, strip using the precomputed class mask.
clsbits &= objc_debug_isa_class_mask;
}
# else
// If not authenticating, strip using the precomputed class mask.
clsbits &= objc_debug_isa_class_mask;
# endif
# else
clsbits &= ISA_MASK;
# endif
return (Class)clsbits;
#endif
}
可见 clsbits &= ISA_MASK;
这样就能计算出类地址来。
以arm64为例
define ISA_MASK 0x0000000ffffffff8ULL
8写作二进制是 1000
f写作二进制是 1111
ISA_MASK 有8个f,所以其就有 4*8=32个1,后面补上8的二进制 1000 ,前面补0
所以 isa & ISA_MASK 就可以得出我们上面操作的到的类地址
大端模式和小端模式
- 大端模式,是指数据的高字节保存在内存的低地址中,而数据的低字节保存在内存的高地址中,这样的存储模式有点儿类似于把数据当作字符串顺序处理:地址由小向大增加,数据从高位往低位放;这和我们的阅读习惯一致。
- 小端模式,是指数据的高字节保存在内存的高地址中,而数据的低字节保存在内存的低地址中,这种存储模式将地址的高低和数据位权有效地结合起来,高地址部分权值高,低地址部分权值低。
举个例子,把 0x12345678
存入内存,假设从地址 0x4000
开始存放
小端模式下
大端模式下