前言
前两篇主要介探索了调用对象的alloc
的底层的流程,定位到核心方法_class_createInstanceFromZone
,该方法主要做了三件事:
- 计算需要开辟的内存空间大小;
- 申请内存;
- 返回地址指针将类与
isa
关联;
本来主要探索 返回的地址指针将类与isa关联
的流程。
NONPOINTER_ISA
探索流程之前,先来了解一下NONPOINTER_ISA
。
目前 Apple的设备使用的都是64的cpu
,寻址能力达到了0-2的64次方
,64
位下程序的指针占用了8字节的内存,但是其实根本用不到这么大内存地址,所以苹果通过优化,存储指针的同时还存储了一些其他的信息,充分利用了这8字节的内存,这就是非指针型ISA(NONPOINTER_ISA),底层是一个isa_t
的联合体。下面是整理之后的isa_t
,由于代码运行在x86 64
位环境下,以该环境下为例:
# define ISA_MASK 0x00007ffffffffff8ULL //用于访问shiftcls存放的指针值
# define ISA_MAGIC_MASK 0x001f800000000001ULL
# define ISA_MAGIC_VALUE 0x001d800000000001ULL
# define ISA_HAS_CXX_DTOR_BIT 1
//这是一个联合体
union isa_t {
isa_t() { }
isa_t(uintptr_t value) : bits(value) { }
uintptr_t bits;
private:
Class cls;
public:
//arm 64字段类似
//这里用了位域去存储
struct {
//nonpointer的值0是纯的ISA指针,1是非指针型的NOPOINTER_ISA指针
uintptr_t nonpointer : 1;
//是否有设置过关联对象
uintptr_t has_assoc : 1;
//是否有C++的析构函数(.cxx_destruct)
uintptr_t has_cxx_dtor : 1;
//存储着Class、Meta-Class对象的内存地址信息
uintptr_t shiftcls : 44;
//用于在调试时分辨对象是否未完成初始化
uintptr_t magic : 6;
//是否有被弱引用指向过
uintptr_t weakly_referenced : 1;
//未被使用
uintptr_t unused : 1;
//标记是否有sitetable结构用于存储引用计数
uintptr_t has_sidetable_rc : 1;
//标记对象的引用计数(首先会存储在该字段中,当到达上限后,在存入对应的引用计数表中)
uintptr_t extra_rc : 8
};
}
复制代码
联合体和位域
什么是联合体
联合体
也称为共合体
,同结构体
一样,也是用来构造数据的一种类型。联合体
的定义如下
union 联合体名{
成员列表
};
复制代码
结构体
和联合体
的区别在于:结构体的各个成员会占用不同的内存,互相之间没有影响;而共用体的所有成员占用同一段内存,占用的内存等于最长的成员占用的内存,修改一个成员会影响其余所有成员,如果对一个成员赋值,就会把其他成员的值覆盖掉。
什么是位域
有些数据在存储的时候并不需要占用一个完整的字节,只需要1位或者几位就即可,比如 nonpointer
字段的值为0和1,只需1位就能够存储,所以C语言提供了一种叫做位域的数据结构。
在结构体定义时,可以在某个成员变量跟上”:”加数字,来限定成员变量占用的位数,如:
struct {
uintptr_t nonpointer : 1;
uintptr_t has_assoc : 1;
...
};
复制代码
isa关联的流程
1.和alloc(上)一样,创建一个Person
对象
int main(int argc, const char * argv[]) {
Person *person1 = [Person alloc];
return 0;
}
复制代码
2.将断点打在_class_createInstanceFromZone
方法,来到这部分代码,此时会调用obj->initInstanceIsa(cls, hasCxxDtor)
if (!zone && fast) {
obj->initInstanceIsa(cls, hasCxxDtor);
} else {
// Use raw pointer isa on the assumption that they might be
// doing something weird with the zone or RR.
obj->initIsa(cls);
}
复制代码
3.进入initInstanceIsa
后,会调用initIsa(cls, true, hasCxxDtor)
,initIsa
主要是创建一个isa_t
结构体对象,赋值之后再赋值给对象结构体的isa
字段,完成关联。
inline void
objc_object::initIsa(Class cls, bool nonpointer, UNUSED_WITHOUT_INDEXED_ISA_AND_DTOR_BIT bool hasCxxDtor)
{
ASSERT(!isTaggedPointer());
isa_t newisa(0);
if (!nonpointer) {
newisa.setClass(cls, this);
} else {
ASSERT(!DisableNonpointerIsa);
ASSERT(!cls->instancesRequireRawIsa());
#if SUPPORT_INDEXED_ISA
ASSERT(cls->classArrayIndex() > 0);
newisa.bits = ISA_INDEX_MAGIC_VALUE;
// isa.magic is part of ISA_MAGIC_VALUE
// isa.nonpointer is part of ISA_MAGIC_VALUE
newisa.has_cxx_dtor = hasCxxDtor;
newisa.indexcls = (uintptr_t)cls->classArrayIndex();
#else
newisa.bits = ISA_MAGIC_VALUE;
// isa.magic is part of ISA_MAGIC_VALUE
// isa.nonpointer is part of ISA_MAGIC_VALUE
# if ISA_HAS_CXX_DTOR_BIT
newisa.has_cxx_dtor = hasCxxDtor;
# endif
newisa.setClass(cls, this);
#endif
newisa.extra_rc = 1;
}
isa = newisa;
}
复制代码
4.来看一看isa_t
的setClass
方法,里面有大量的IF
宏判断,但此时环境下最终执行的是shiftcls = (uintptr_t)newCls >> 3
,也就是将类地址的指针右移3位赋给shiftcls
。
inline void
isa_t::setClass(Class newCls, UNUSED_WITHOUT_PTRAUTH objc_object *obj)
{
// Match the conditional in isa.h.
#if __has_feature(ptrauth_calls) || TARGET_OS_SIMULATOR
...
#else // Nonpointer isa, no ptrauth
shiftcls = (uintptr_t)newCls >> 3;
#endif
}
复制代码
为什么地址要右移3位呢?带着这个问题来看看取类的指针地址
的时候是如何操作的。
5.有setClass
,必然会有getClass
,如下,ISA_MASK
就是上面定义的宏常量。
inline Class
isa_t::getClass(MAYBE_UNUSED_AUTHENTICATED_PARAM bool authenticated) {
#if SUPPORT_INDEXED_ISA
...
# else
clsbits &= ISA_MASK;
# endif
return (Class)clsbits;
#endif
}
复制代码
6.通过lldb
获取了 person1
实例对象的isa
和Person
的类对象指针地址,
我们将此时isa
和ISA_MASK
转为二进制来进行与运算,会更直观
可以看到,& ISA_MASK
其实就是抹去了其他位数上的值,由于在4.
时setClass
方法中将地址右移了3位
,此时低3位的0
正好补上右移的3位
,且由于是8字节对齐低三位为0,也不刽有数据丢失。
总结
至此,通过调试objc
源码完成了对alloc
流程的探索。整个流程探索下来,发现了更多值得探索的东西,探索的越多,愈发觉得自己了解的越少,底层探索才刚刚开始。