该文章是对前一篇 OC底层原理01-alloc流程探索 中initInstanceIsa
(给对象关联isa)分支的一个拓展和深入
一、准备工作
-
objc4
可编译源码,可直接跳到文章最后,下载调试好的源码
二、源码里面找isa
2.1 继续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);
}
- 我们自定义的类都是走
initInstanceIsa
- 系统某一些类才会调用
initIsa
2.2 进入initInstanceIsa
inline void
objc_object::initInstanceIsa(Class cls, bool hasCxxDtor)
{
ASSERT(!cls->instancesRequireRawIsa());
ASSERT(hasCxxDtor == cls->hasCxxDtor());
initIsa(cls, true, hasCxxDtor);
}
inline void
objc_object::initIsa(Class cls)
{
initIsa(cls, false, false);
}
-
initInstanceIsa
也会进入initIsa
- 它们第二个参数
nonpointer
不同 -
initInstanceIsa
是传的true,initIsa
是传的false
2.3 进入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;
}
}
- 由于我们自定义的类
nonpointer
都是true,所以我们会进入else
流程 - 发现目标
isa
,它是一个isa_t
类型的对象 -
isa_t newisa(0)
初始化一个isa
-
newisa.bits = hasCxxDtor
给属性bits
赋值 -
newisa.has_cxx_dtor = hasCxxDtor
给属性has_cxx_dtor
赋值 -
newisa.shiftcls = (uintptr_t)cls >> 3
给属性shiftcls
赋值,并向右移三位 -
isa = newisa
把新建的newisa
赋值给对象的isa
,进行关联
2.4 查看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
是一个联合体,属性互斥。 -
cls
和bits
不能同时赋值
2.5 查看ISA_BITFIELD
# 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
就走上面,如果是x86_64
就走下面 - 因为我们配置的
objc4
是基于macos
的,所以我们要研究的是下面 -
isa
存在内存地址的第一段,也就是8字节,8*8=64位
2.6 断点分析newisa
的赋值过程
2.6.1 newisa.bits = ISA_MAGIC_VALUE
在这一行下断点
2.6.2 打印newisa
(lldb) p newisa
(isa_t) $2 = {
cls = nil
bits = 0
= {
nonpointer = 0
has_assoc = 0
has_cxx_dtor = 0
shiftcls = 0
magic = 0
weakly_referenced = 0
deallocating = 0
has_sidetable_rc = 0
extra_rc = 0
}
}
- 初始都为空
2.6.3 断点往下走一步,再次打印newisa
(lldb) p newisa
(isa_t) $3 = {
cls = 0x001d800000000001
bits = 8303511812964353
= {
nonpointer = 1
has_assoc = 0
has_cxx_dtor = 0
shiftcls = 0
magic = 59
weakly_referenced = 0
deallocating = 0
has_sidetable_rc = 0
extra_rc = 0
}
}
-
cls
被赋值了0x001d800000000001
-
bits
被赋值了8303511812964353
-
nonpointer
被赋值了1
-
magic
被赋值了59
2.6.3.1 系统给cls
赋值的是什么
- 宏
ISA_BITFIELD
上面一个宏# define ISA_MAGIC_VALUE 0x001d800000000001ULL
,这一步可以看做把ISA_MAGIC_VALUE
直接赋值给cls
,与打印台打印的cls
不是同一个,这里的cls
是newisa
中的cls
- 这里的
ULL
是unsigned long long
无符号长整型,只是一个类型标识
2.6.3.2 系统给bits
赋值的是什么
- 原来系统把宏
ISA_MAGIC_VALUE
的值0x001d800000000001
转成了10进制,赋给了newisa
中的cls
- 注意:上图中打印的
cls
,是objc_object::initIsa(Class cls, bool nonpointer, bool hasCxxDtor)
方法传入的,为了验证当前研究的是GomuPerson
的流程
2.6.3.3 系统给nonpointer
赋值的是什么
- 因为
initIsa
方法传入的是一个true,所以赋值为1
2.6.3.4 系统给magic赋值的是什么
- 用
p/t
命令将0x001d800000000001
转成2进制打印,得上以上截图的一串数字 - 由宏
ISA_BITFIELD
的位域定义可以得到,magic
占位是6位
,从第47位到第52位
,由于是iOS小端
,从右到左读取,用分隔符每4位分开方便查看0b0000_0000_0001_1101_1000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0001
,得到第47位到52位为:111011
-
111011
是2进制
,转成10进制
得到59
2.6.4 断点继续往下走一步,再次打印newisa
(lldb) p newisa
(isa_t) $7 = {
cls = 0x001d800000000005
bits = 8303511812964357
= {
nonpointer = 1
has_assoc = 0
has_cxx_dtor = 1
shiftcls = 0
magic = 59
weakly_referenced = 0
deallocating = 0
has_sidetable_rc = 0
extra_rc = 0
}
}
-
has_cxx_dtor
被赋值了1
,前面传过来hasCxxDtor
为true
2.6.4 断点继续往下走一步,再次打印newisa
(lldb) p newisa
(isa_t) $8 = {
cls = GomuPerson
bits = 8303516107940741
= {
nonpointer = 1
has_assoc = 0
has_cxx_dtor = 1
shiftcls = 536872048
magic = 59
weakly_referenced = 0
deallocating = 0
has_sidetable_rc = 0
extra_rc = 0
}
}
-
cls
的值变成了GomuPerson
,说明isa
和GomuPerson
已关联 -
bits
的值由8303511812964357
变成了8303516107940741
-
shiftcls
被赋值了536872048
2.6.4.1 shiftcls
赋的值是什么
- 因为
newisa.shiftcls = (uintptr_t)cls >> 3
这一步代码,将cls
强转成uintptr_t
(无符号长整型的别名),然后右移位 - 打印
(uintptr_t)cls
得到4294976384
- 将
4294976384
右移三位,得到536872048
- 这一步的目的就是将
cls
强转后右移三位的值赋给shiftcls
- 因为
newisa
中又给shiftcls
赋了值,所以bits
必然会变
2.6.4.2 bits
的值为什么变了
[图片上传中...(image.png-902d6d-1599838772142-0)]
- 把
bits
当前的值换算成2进制打印,得到上图$27
- 把
shiftcls
的值换算成2进制打印,得到上图$28
- 把
$28
前面补零,补足44位,则与$27
完全相同 - 说明
isa
中3-46
位的确存的是cls
,但是是右移了3位
的cls
2.6.5 反向由person
的首地址isa
推导出shiftcls
2.6.5.1 打印person
的isa
的地址,转成10进制
- 得到
8303516107940741
,很上面newisa
中的bits
完全一样
2.6.5.2 由isa
的地址推导给shiftcls
赋值之前的(uintptr_t)cls
- 由于
shiftcls
在isa
内存的3-46
位,所以我们要先抹掉前面3位,右移>>
3位 - 左边空出20位,左移
<<
20位,再右移>>
17位,把左边17位抹零
- 得到结果
4294976384
,和给shiftcls
赋值的(uintptr_t)cls
的值一模一样
2.6.5.3 shiftcls
赋值的时候将(uintptr_t)cls
右移三位的目的
- 需要将一个64位的
(uintptr_t)cls
赋值给一个从isa
第三位开始长度只有44的shiftcls
- 那么我们需要取
(uintptr_t)cls
的0-43
位赋值给shiftcls
,因为shiftcls
是从isa
的第三位开始,这样赋值的后果是shiftcls
在isa
中的值就会向左偏移移3位, - 为了保证赋值后的
shiftcls
和(uintptr_t)cls
相等,我们必须把(uintptr_t)cls
的3-46
赋值给shiftcls
,即(uintptr_t)cls>>3
2.6.6 isa
的地址 &
ISA_MASK
,会得到 GomuPerson
- 经验证,
isa
的地址&
ISA_MASK
,确定会得到GomuPerson
- 把
ISA_MASK
转成二进制
,发现只有中间3-46
位有效,所以isa
与上ISA_MASK
,相当于把前3位
和后17位
置0
2.7 基于x86_64
的位域图
-
nonpointer
:表示是否对isa
指针开启指针优化,占1位-
0
:纯isa
指针 -
1
:不只是类对象地址
,isa
中包含了类信息
、对象的引用计数
等
-
-
has_assoc
:表示关联对象标志位,占1位-
0
:没有关联对象 -
1
:存在关联对象
-
-
has_cxx_dtor
:表示该对象是否有C++/OC的析构器
(dealloc
),占1位-
0
:没有析构函数,则可以更快的释放对象 -
1
:有析构函数,则需要做析构逻辑
-
-
shiftclx
表示存储类的指针的值
(类的地址), 即类信息-
arm64
中占33
位,开启指针优化
的情况下,在arm64
架构中有33
位用来存储类指针 -
x86_64
中占44
位
-
-
magic
:用于调试器判断当前对象是真的对象
还是没有初始化的空间
,占6位 -
weakly_refrenced
:表示对象是否被指向
或者曾经指向一个ARC
的弱变量
,没有弱引用的对象
可以更快释放
,占1位 -
deallocating
:标志对象是是否正在释放内存
,占1位 -
has_sidetable_rc
:表示当对象引用计数大于10
时,则需要借用该变量
存储进位
,占1位 -
extra_rc
:(额外的引用计数) --- 导尿管表示该对象的引用计数值
,实际上是引用计数值减1
,如果对象的引用计数为10
,那么extra_rc
为9
,占8位
四、拓展知识
构造数据类型的方式:
-
结构体
[struct
]
结构体
是指把不同的数据
组合成一个整体
,其变量是共存的
,变量不管是否使用,都会分配内存
。
优点:存储容量较大
,包容性强
,且成员之间不会相互影响
缺点:所有属性都分配内存
,比较浪费内存
,假设有4个int成员,一共分配了16字节的内存,但是在使用时,你只使用了4字节,剩余的12字节就是属于内存的浪费
-
联合体
[union
]
联合体
也是由不同的数据
类型组成,但其变量是互斥的
,所有的成员共占一段内存
。而且联合体
采用了内存覆盖技术
,同一时刻
只能保存一个成员的值
,如果对新的成员赋值
,就会将原来成员的值覆盖掉
优点:所有成员共用一段内存
,使内存的使用更为精细灵活
,同时也节省了内存空间
缺点:包容性弱