在前面的OC底层-对象的alloc流程探究文章中,alloc
的流程中,我们知道了OC
底层是通过initInstanceIsa
把我们的类cls
和isa
关联起来,我们顺着initInstanceIsa
去对今天的主角isa
一探究竟。
isa
是什么
isa
的类型
struct objc_object
private:
isa_t isa;
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
};
可以看到在objc_object
结构体中,isa
是isa_t
类型。同时我们找到了isa_t
的定义。union
是联合体,这里我们的isa
显然就是联合体类型。
union
和 struct
union
联合体
(也称共用体
)是由不同的数据类型组成,但其成员是互斥
的,所有的成员共占一段内存
。联合体采用了内存覆盖
技术,同一时刻只能保存一个成员的值,如果对新的成员赋值,就会将原来成员的值覆盖掉。
- 优点:所有成员共用一段内存,节省了内存空间。
- 缺点:包容性弱
struct
结构体
是指把不同的数据组合成一个整体,其成员是共存
的,成员不管是否使用,都会分配内存
。
- 优点:存储容量较大,包容性强,且成员之间不会相互影响。
- 缺点:所有成员不管是否使用都分配内存,比较浪费内存。
两者的区别
- 内存占用情况
- 联合体的所有成员占用同一段内存,成员互斥
- 结构体的各个成员会占用不同的内存,互相之间没有影响
- 内存分配大小
- 联合体占用的内存等于最大的成员占用的内存
- 结构体内存等于最大的成员占用的内存的整数倍
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
};
# 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
通过联合体
和结构体
的特性,不难分析出isa_t
采用联合体
是基于内存优化
的考虑。isa指针
的内存大小是8
字节,即64
bit,也就是64
位。isa
通过char+位域
的设计,巧妙的用二进制的每一位来存储和表示联合体
成员的信息。
isa_t
提供了的对应的初始化方法isa_t() { }
和isa_t(uintptr_t value) : bits(value) { }
。isa_t
还提供了2个成员。cls
和bits
,以及一个结构体定义的位域
,用来存储类的信息。-
分析下
位域
中存储的每个数据-
nonpointer
:表示是否对isa
指针开启指针优化。0:纯isa指针,1:不止是类对象地址,isa 中包含了类信息、对象的引用计数等。 -
has_assoc
:关联对象标志位,0没有,1存在 -
has_cxx_dtor
:该对象是否有C++
或者Objc
的析构器,如果有析构函数,则需要做析构逻辑,如果没有,则可以更快的释放对象 -
shiftcls
:存储类指针的值。开启指针优化的情况下,在arm64
架构中有33
位用来存储类指针。 -
magic
:用于调试器判断当前对象是真的对象还是没有初始化的空间。 -
weakly_referenced
:标志对象是否被指向或者曾经指向一个ARC
的弱变量,没有弱引用的对象可以更快释放。 -
deallocating
:标志对象是否正在释放内存 -
has_sidetable_rc
:当对象引用技术大于10
时,则需要借用该变量存储进位 -
extra_rc
:当表示该对象的引用计数值,实际上是引用计数值减1
, 例如,如果对象的引用计数为10
,那么 extra_rc 为9
。如果引用计数大于10
, 则需要使用到下面的has_sidetable_rc
。
-
下面是x86_64
即macOS
平台的isa_t
的成员存储情况
[图片上传失败...(image-519ad0-1600051522675)]
isa
做了什么
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
成员赋值
- 和
alloc
一样,我们在main.m
中初始化MuPerson
。由alloc
->_objc_rootAlloc
->callAlloc
->_objc_rootAllocWithZone
>_class_createInstanceFromZone
>initInstanceIsa
>initIsa
到上面这份核心代码,打上断点,开始调试,我们来看系统是如何对isa
的成员赋值。 - 初始化
isa
指针,这里依据nonpointer
来分别通过cls
和bits
对isa
初始化,也从侧面验证了cls
和bits
互斥的原则,不能同时setter
。 - 断点过
isa_t newisa(0)
,对isa
初始化,到bits
赋值之前,我们通过p
打印newisa
结果如下
- 断点过
newisa.bits = ISA_MAGIC_VALUE
,对bits
赋值之后,我们通过p
打印newisa
结果如下
- 断点到
isa = newisa
,对isa
赋值之后,我们通过p
打印newisa
结果如下
- 三次断点
p
打印的newisa
结果做下对比如下
通过断点,我们清晰的看到isa
中的成员是如何一步一步的被赋值。
isa
和类
关联
通过对isa
成员赋值分析,isa
和我们类
的关联关键在于isa
中的成员shiftcls
,即newisa.shiftcls = (uintptr_t)cls >> 3
这句关键代码。这里>> 3
是为了通过位运算把cls
的信息准确的放在shiftcls
的存储位置上。以这样一种巧妙的方式,就把isa
和类
关联起来。
isa
是否和类关联的验证
- 通过
isa
&ISA_MSAK
验证
(lldb) po cls
MuPerson
(lldb) po obj
<MuPerson: 0x1007076b0>
(lldb) x/4gx obj
0x1007076b0: 0x001d800100002255 0x0000000000000000
0x1007076c0: 0x0000000000000000 0x0000000000000000
(lldb) po 0x001d800100002255 & 0x00007ffffffffff8ULL
MuPerson
(lldb)
- 通过
object_getClass
验证
object_getClass
-> Class object_getClass
-> inline Class objc_object::getIsa()
-> inline Class objc_object::ISA()
inline Class
objc_object::ISA()
{
ASSERT(!isTaggedPointer());
#if SUPPORT_INDEXED_ISA
if (isa.nonpointer) {
uintptr_t slot = isa.indexcls;
return classForIndex((unsigned)slot);
}
return (Class)isa.bits;
#else
return (Class)(isa.bits & ISA_MASK);
#endif
}
- 通过
位运算
验证
(lldb) p cls
(Class) $7 = MuPerson
(lldb) p/x cls
(Class) $8 = 0x0000000100002250 MuPerson
(lldb) p obj
(MuPerson *) $9 = 0x000000010192c440
(lldb) x/4gx obj
0x10192c440: 0x001d800100002255 0x0000000000000000
0x10192c450: 0x0000000000000000 0x0000000000000000
(lldb) p/x 0x001d800100002255 >> 3
(long) $11 = 0x0003b0002000044a
(lldb) p/x $11 << 20
(long) $12 = 0x0002000044a00000
(lldb) p/x $12 >> 17
(long) $13 = 0x0000000100002250
(lldb)
为什么设计isa
isa
走位流程图
这里放上业界经典isa
走位流程图
- 子类的isa走位链:
子类对象 --> 子类 --> 子元类 --> NSObject(根元类) --> NSObject(根元类,即自己)
- 父类的isa走位链:
父类对象 --> 父类 --> 父元类 --> NSObject(根元类) --> NSObject(根元类,即自己)
- 类的继承关系链:
子类 --> 父类 --> NSObject(根类)--> nil
- 元类的继承关系链:
子元类 --> 父元类 --> 根元类 --> NSObject(根类)--> nil
objc_class
& objc_object
struct NSObject_IMPL {
Class isa;
};
typedef struct objc_class *Class;
struct objc_class : objc_object {
// Class ISA;
Class superclass;
cache_t cache; // formerly cache pointer and vtable
class_data_bits_t bits; // class_rw_t * plus custom rr/alloc flags
class_rw_t *data() const {
return bits.data();
}
void setData(class_rw_t *newData) {
bits.setData(newData);
}
///此处省略一万行
}
struct objc_object {
private:
isa_t isa;
///此处省略一万行
}
-
clang
编译的mian.cpp
文件中,NSObject
中的isa
在底层是由Class
定义的,其中Class
的底层编码来自objc_class
类型,而objc_class
又继承于objc_object
。 -
OC
层面的根类NSObject
,初始化对象
就会有isa
特性,isa
的根源来自OC
底层的objc_object
。 - 所有的
类
是由objc_class
为模板生成的,所有的对象
则是由objc_object
为模板生成的。
贯穿全局的isa
上面我们微观
的分析了isa
做了什么,isa
把对象
和类
关联起来。宏观的看,isa
贯穿了我们的类对象
、类
、元类
、根元类
。在OC
底层系统为我们设计了objc_object
、objc_class
,OC
上层由NSObject
作为根类持有isa
特性。isa
完美的将底层
和OC
串联起来。
结语:万物皆对象
,万物皆有isa
。