前文回顾:
之前已经通过clang
查看过对象在C++
是什么样子的了,接下来结合源码分析下对象的本质。
isa探寻
首先在源码中查看objc_class的定义,并没有发现什么有用的价值。objc_class
只是包含了一个Class isa
,而Class
只是objc_class
的一个指针别名,好一个套娃。当然,这是是OBJC
里面,但是我们一般用的都是__OBJC2__
。找到我们objc_class的定义,objc_class节后提继承与objc_object,因为代码太长,这里就简单显示一个截图。这个也只晓得是一个指针,指针里面放了啥还是不知道。
typedef struct objc_class *Class;
struct objc_class {
Class _Nonnull isa OBJC_ISA_AVAILABILITY;
#if !__OBJC2__
Class _Nullable super_class OBJC2_UNAVAILABLE;
const char * _Nonnull name OBJC2_UNAVAILABLE;
long version OBJC2_UNAVAILABLE;
long info OBJC2_UNAVAILABLE;
long instance_size OBJC2_UNAVAILABLE;
struct objc_ivar_list * _Nullable ivars OBJC2_UNAVAILABLE;
struct objc_method_list * _Nullable * _Nullable methodLists OBJC2_UNAVAILABLE;
struct objc_cache * _Nonnull cache OBJC2_UNAVAILABLE;
struct objc_protocol_list * _Nullable protocols OBJC2_UNAVAILABLE;
#endif
} OBJC2_UNAVAILABLE;
接下来只能回顾到源码对象alloc
中,因为在过程中出现过initInstanceIsa
,只能顺着这个查看了。顺利找到关键步骤objc_object::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;
}
// 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) { }
uintptr_t bits;
private:
// Accessing the class requires custom ptrauth operations, so
// force clients to go through setClass/getClass by making this
// private.
Class cls;
public:
#if defined(ISA_BITFIELD)
struct {
ISA_BITFIELD; // defined in isa.h
};
bool isDeallocating() {
return extra_rc == 0 && has_sidetable_rc == 0;
}
void setDeallocating() {
extra_rc = 0;
has_sidetable_rc = 0;
}
#endif
然后在宏定义ISA_BITFIELD
,进入宏定义看到了苹果告诉我们isa里面是怎么放的了,在ios模拟器和真机不一样,macos也不一样。
isa结构
nonpointer
nonpointer
表示对指针时候开启优化 0:纯isa指针,1:不止包含对象地址,还包含类信息、对象引用计数、弱引用等。
has_assoc
has_assoc
是否有设置过关联对象,如果没有,释放时会更快
has_cxx_dtor
has_cxx_dtor
是否有C++的析构函数(.cxx_destruct),如果没有,释放时会更快
shiftcls
shiftcls
存储着Class、Meta-Class对象的内存地址信息,在nonpointer=1的情况下,在arm64架构中有33位用来存储类指针 (重点)
magic
magic
用于在调试时分辨对象是否未完成初始化
weakly_referenced
weakly_referenced
是否有被弱引用指向过,如果没有,释放时会更快。弱引用表。
deallocating
deallocating
对象是否正在释放
has_sidetable_rc
has_sidetable_rc
引用计数器是否过大无法存储在isa中
如果为1,那么引用计数会存储在一个叫SideTable
的类的属性中
extra_rc
extra_rc
里面存储的值是引用计数器减1,如果引用计数10这里面存的9,如果引用计数大于10则需要使用has_sidetable_rc
。
isa分析
isa_t
是一个联合体,内部有成员变量bits
和cls
,还有一个匿名结构体类型,里面包含ISA_BITFIELD
,这个ISA_BITFIELD
使用位域完美的存放了类指针以及相关的信息,告诉我们这个isa里面存放的是什么!!isa
分为nonpointer
类型和非nonpointe
r。非nonpointer
类型只是一个纯指针,nonpointer
还包含了类的其他信息。isa
是联合体+位域的方式存储信息的。采用这种方式的可以节省大量内存。万物皆对象,只要是对象就有isa
指针,大量的isa
就占用了很多内存,联合体公用一块内存节省了部分内存,而位域更是在节省内存的基础上存储了更多的信息,充分的将这个8字节
利用的满满的,这个可以让我们很清晰的看到isa
每一位存放的数据。
isa实践探究
通过initIsa
来看看isa
的初始过程。
1. 在objc_object::initIsa
打上断点等到LQPeople
来到。首先是通过isa_t
的构造翻身构造了一个isa_t
的联合体
2. 然后将bits
赋值了ISA_MAGIC_VALUE
,ISA_MAGIC_VALUE
为宏定义0x001f800000000001ULL
,使用二进制查看后可以看到nonpointer
为1
,magic
为59
。
3. 接下来来到了setClass
,这里将cls
也就是LQpeople
传入了。
4. 进入到setClass
,这里给shiftcls
赋值了,值为cls
右移3
位。
5. 通过lldb
查看一下newisa
。
6. 最后就是给extra_rc
(引用计数器)赋值了。extra_rc
占用8位。
7. 到这里initIsa就走完了,接下来去代码里面验证
在代码里面验证isa
这里看到p
对象首地址存的就是我们刚刚的isa
!
isa反推class指针
1. 直接补位
找到shiftisa然后二进制查看,前面补17个零后面不3个,因为这是macos环境,所以是这样操作的。这个方法太笨了。
2.位运算
用位运算挤掉低3位以及高17位,剩下的就是我们的class指针了
3. 与上掩码
与上ISA_MASK
一步到位!