前言
通过 iOS 底层原理之 alloc & init & new 探究这篇文章,我们知道alloc
一个对象,最核心的三个方法cls->instanceSize
计算内存大小 , (id)calloc(1, size)
开辟内存返回地址指针, obj->initInstanceIsa
初始化isa
关联类,那么isa
结构是到底是什么样的呢?到底是怎么关联的呢?下面我们进行探究
准备工作
联合体(union)
定义:当多个数据需要共享内存或者多个数据每次只取其一时 ,可以利用联合体(union)
- 联合体可以定义多个不同类型的成员,但是联合体的
内存大小
由其中最大的成员的大小
决定 - 联合体变量共享同一块内存大小,成员之间是
互斥的
,一次只能使用其中的一个成员 - 对联合体中某一个成员赋值,会
覆盖
其它成员的值 - 它的
所有成员
都是从offset
为0
的地方开始存储的
位域(Bit field)
有些信息在存储时,并不需要占用一个完整的字节, 而只需占 几个
或一个
二进制位。例如在存放一个只有0
和1
两种状态成员, 用一位二进位即可,节省存储空间,并使处理简便
下面探讨isa
结构时,会对联合体
&位域
进行实例探讨
isa 结构分析
我们按照alloc
的流程,走到obj->initInstanceIsa
,那么接着往下走
inline void
objc_object::initInstanceIsa(Class cls, bool hasCxxDtor)
{
ASSERT(!cls->instancesRequireRawIsa());
ASSERT(hasCxxDtor == cls->hasCxxDtor());
initIsa(cls, true, hasCxxDtor);
}
点击进入 initIsa
inline void
objc_object::initIsa(Class cls, bool nonpointer, bool hasCxxDtor)
{
ASSERT(!isTaggedPointer());
if (!nonpointer) {
isa = isa_t((uintptr_t)cls);
} else {
ASSERT(!DisableNonpointerIsa);
ASSERT(!cls->instancesRequireRawIsa());
isa_t newisa(0);
#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
newisa.has_cxx_dtor = hasCxxDtor;
newisa.shiftcls = (uintptr_t)cls >> 3;
#endif
// 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;
}
}
点击进入isa_t
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
};
代码中可以看出isa_t
是一个联合体,占8字节
,既64位
,优化之前isa
只是一个指向类或者元类指针,优化之后采用联合体+位域
存储更多的信息,提高了内存的利用率
-
isa_t
结构体中有两个成员cls
和bits
,isa_t
是联合体,所以cls
和bits
是互斥的
,既只能同时给一个变量赋值 - 在
isa_t
中ISA_BITFIELD
是一个宏
,用来区分不同的架构既arm64
(真机)和x86_64
bits的64位存储分布图(已真机为例)
各存储变量的含义:
-
nonpointer
:表示是否对isa
指针进行优化,0
表示纯指针,1
表示不止是类对象的地址,isa
中包含了类信息、对象、引用计数等 -
has_assoc
:关联对象标志位,0
表示没有,1
表示有 -
has_cxx_dtor
:该对象是否C ++
或者Objc
的析构器,如果有析构函数,则需要做析构逻辑,没有,则释放对象 -
shiftcls
:储存类指针的值,开启指针优化的情况下,在arm64
架构中有33
位用来存储类指针,x86_64
架构中占44
位 -
magic
:用于调试器判断当前对象是真的对象还是没有初始化的空间 -
weakly_referenced
:指对象是否被指向或者曾经指向一个ARC
的弱变量,没有弱引用的对象可以更快释放 -
deallocating
:标志对象是否正在释放 -
has_sidetable_rc
:当对象引用计数大于10
时,则需要借用该变量进位 -
hextra_rc
:表示该对象的引用计数值,实际上引用计数值减1
,例如,如果对象的引用计数为10
,那么extra_rc
为9
,如果大于10
,就需要用到上面的has_sidetable_rc
isa_t
总结,优化之后的isa
,包含了优化之前类的指针shiftcls
,仍然可以通过isa
找到对应的类。同时还包含了更多其他的内容
isa 关联类
首先我们定义一个LWPerson
类,初始化LWPerson alloc
,流程如下:alloc
->_class_createInstanceFromZone
-> initInstanceIsa
->initIsa
,现在我们探究initIsa
方法,isa
是怎么和类关联起来的
从图中断点走位来看nonpointer
=1
,说明isa
不是纯指针
,打印看下newisa
包含了哪些信息
newisa.bits = ISA_MAGIC_VALUE
给bits
一个初始值,而ISA_MAGIC_VALUE
是一个宏 # define ISA_MAGIC_VALUE 0x001d800000000001ULL
,0x001d800000000001
转二进制
可以看到第一位是1
nonpointer
值也是1
,在x86_64
中 magic
的位域分布47 - 52
位,占6
位,打印中显示 magic = 59
,把它转成二进制
很明显可以看出,bits
中magic
的59
是被ISA_MAGIC_VALUE
赋值的
接着往下看
总结:从图中cls = LWPerson
可以看出此时的isa
已经关联了类,shiftclass
保存了类信息
探究 newisa.shiftcls = (uintptr_t)cls >> 3
我们按照 cls >> 3
算法寄过打印的值和shiftcls
的值是一样的,验证了shiftcls
存储着类信息
下面再添加一种验证算法
因为在
x86_64
中 ,shiftcls
占44
位,从3 - 46
位,将isa
的内存地址右移3
位,再左移20
位,再右移17
位,这时再打印地址isa
的地址, 发现就是LWPerson
,说明isa
和类是关联的
isa
指针移动图,暂且先不画了(可参照”bits的64位存储分布图“,进行移动),要是不明白的可以留言,或者后面把图补上