本章分析
isa与类的关联
0x00 -- 联合体 & 位域
01联合体 union
union是c/c++里的自定义复合数据类型,与struct类似, 但是它们所占用的内存空间不一样。
参考我写的
01 -- 对象指针地址初始化isa & 关联类信息
在_class_createInstanceFromZone这个方法里下面👇的代码片段。
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);
}
主要做了初始化isa_t ias指针,关联类信息
inline void
objc_object::initIsa(Class cls, bool nonpointer, bool hasCxxDtor)
{
ASSERT(!isTaggedPointer());
if (!nonpointer) {
// 不是 nonpointer
isa = isa_t((uintptr_t)cls);
} else {
// 是 nonpointer(不是一个简单的指针) 被优化过的isa指针
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;
}
}
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
};
- 首先判断类是不是
nonpointer,自己创建的类都是nonpointer, 系统内建的类绝大部分是非nonpointer。 - 初始化一个
isa_t的union变量newisa, 使用联合体的初始化方法,成员变量都初始化为0
- 走到宏定义
SUPPORT_INDEXED_ISA的else分支里,把ISA_MAGIC_VALUE给到bits,来初始化各个联合体位域
# 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,当前运行环境为mac端,所以走x86_64的逻辑。
1. nonpointer 表示自定义的类,占1位
- 0 纯
isa - 1 是
nonpointer, 不只是只有类对象地址,还包括了其它信息,如类信息,引用计数等。
2. has_assoc 表示关联对象标识,占1位
- 0 没有关联对象
- 1 有关联对象
3. has_assoc 表示该对象是否有c++/oc析构函数, 占1位
- 如果有析构函数, 需要做析构逻辑
- 没有,则可以更快释放对象
4.shiftcls,存储类地址,即类信息
- 在
arm64架构占33位 - 其它占44位
5. magic 用于调试器判断当前对象是真的对象还是没有初始化空间,占6位
6. weakly_referenced 表示当前对象是否被指向或者曾经指向一个ARC弱变量。
7.deallocating 表示当前对象是否正在释放内存
8. has_sidetable_rc 表示当前对象引用计数大于0的时候,需要借用该变量存储进位
9. extra_rc 表示当前对象的引用计数值,实际上是引用计数值减1,例如引用计数为10, 那么这个extra_rc为9。如果大于10,需要用到has_sidetable_rc
0x02 -- 进一步探索isa的初始化以及绑定cls
在objc_object::initIsa中,
isa_t newisa(0);执行完, 通过LLDB调试看一下当前内存布局
过了当前断点, 通过union的构造函数isa_t(uintptr_t value) : bits(value) { }使其各个位置都为0
# define ISA_MAGIC_VALUE 0x000001a000000001ULL // arm64
# define ISA_MAGIC_VALUE 0x001d800000000001ULL // x86_64
newisa.bits = ISA_MAGIC_VALUE;
magic在x86_64下占6位, 是47到52位,
通过这俩张图所占位数与计算, magic的值是59.
接着把newisa.shiftcls = (uintptr_t)cls >> 3;赋值
shiftcls存储着它的类对象的指针。这俩是关联类的关键。
因为newisa要存储cls的值 ,cls也有isa,所以要把低三位左移抹掉, 防止赋值的时候把cls的高三位就不能正确的存储到shiftcls中。
通过打印cls左移3位得出值与newisa 的shiftcls存储的值一毛一样。
最后isa = newisa; 把newisa返回给对象isa,这里就把cls关联到对象isa
0x03 -- 通过isa获取类
对位运算不熟悉,可以参考的我的学会位运算,助力开发高性能
- 通过拿
isa掩码位与来获取 - 通过左移和右移得到
shiftcls来获取 - 通过
runtime api的验证
- 方式一
得出类对象
- 方式二
左移和右移也能将shiftcls得到, 把低三位和高17的信息抹掉,只留下中间的信息。就是class的地址。
- 方式三
通过object_getClass获取类信息
Class object_getClass(id obj)
{
if (obj) return obj->getIsa();
else return Nil;
}
inline Class
objc_object::getIsa()
{
if (fastpath(!isTaggedPointer())) return ISA();
extern objc_class OBJC_CLASS_$___NSUnrecognizedTaggedPointer;
uintptr_t slot, ptr = (uintptr_t)this;
Class cls;
slot = (ptr >> _OBJC_TAG_SLOT_SHIFT) & _OBJC_TAG_SLOT_MASK;
cls = objc_tag_classes[slot];
if (slowpath(cls == (Class)&OBJC_CLASS_$___NSUnrecognizedTaggedPointer)) {
slot = (ptr >> _OBJC_TAG_EXT_SLOT_SHIFT) & _OBJC_TAG_EXT_SLOT_MASK;
cls = objc_tag_ext_classes[slot];
}
return cls;
}
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); // 也是拿bits去和掩码做位与运算
#endif
}