该文章是对前一篇 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在这一行下断点

image.png
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赋值的是什么

image.png
- 宏
ISA_BITFIELD上面一个宏# define ISA_MAGIC_VALUE 0x001d800000000001ULL,这一步可以看做把ISA_MAGIC_VALUE直接赋值给cls,与打印台打印的cls不是同一个,这里的cls是newisa中的cls - 这里的
ULL是unsigned long long无符号长整型,只是一个类型标识
2.6.3.2 系统给bits赋值的是什么

image.png
- 原来系统把宏
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赋值的是什么

image.png
- 用
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赋的值是什么

image.png
- 因为
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进制

image.png
- 得到
8303516107940741,很上面newisa中的bits完全一样
2.6.5.2 由isa的地址推导给shiftcls赋值之前的(uintptr_t)cls

image.png
- 由于
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

image.png
- 经验证,
isa的地址&ISA_MASK,确定会得到GomuPerson - 把
ISA_MASK转成二进制,发现只有中间3-46位有效,所以isa与上ISA_MASK,相当于把前3位和后17位置0
2.7 基于x86_64的位域图

image.png
-
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]
联合体也是由不同的数据类型组成,但其变量是互斥的,所有的成员共占一段内存。而且联合体采用了内存覆盖技术,同一时刻只能保存一个成员的值,如果对新的成员赋值,就会将原来成员的值覆盖掉
优点:所有成员共用一段内存,使内存的使用更为精细灵活,同时也节省了内存空间
缺点:包容性弱