背景
在上篇对象alloc那点事中分析对象创建过程,最终是走到_class_createInstanceFromZone方法,这个方法中有三个关键步骤:
//确认创建类需要开辟的内存大小
size = cls->instanceSize(extraBytes);
//开辟内存
obj = (id)calloc(1, size);
//把isa信息填充到上面申请到的内存中
obj->initInstanceIsa(cls, hasCxxDtor);
其中instanceSize在OC对象大小那点事已经分析过了,而关于calloc涉及到比较底层的知识,有兴趣可以参考iOS 高级之美(六)—— malloc分析,今天我们来看一下initInstanceIsa这个方法到底干了啥~
一、initInstanceIsa方法执行流程
objc_object::initInstanceIsa(Class cls, bool hasCxxDtor)
{
initIsa(cls, true, hasCxxDtor);
}
inline void
objc_object::initIsa(Class cls, bool nonpointer, bool hasCxxDtor)
{
ASSERT(!isTaggedPointer());
//是否对isa开启指针优化,所有自定义的oc类都是开启的
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
isa = newisa;
}
}
查看SUPPORT_INDEXED_ISA的定义:
#if __ARM_ARCH_7K__ >= 2 || (__arm64__ && !__LP64__)
# define SUPPORT_INDEXED_ISA 1
#else
# define SUPPORT_INDEXED_ISA 0
#endif
__ARM_ARCH_7K__
: 是否手表系统__arm64__
: 是否arm架构__LP64__
:是否64位系统
在mac电脑上进行debug调试,非手表、非arm架构、64位系统,执行到此处SUPPORT_INDEXED_ISA为0。所以关键代码是
//给bits一个初始值
newisa.bits = ISA_MAGIC_VALUE;
//标记是否有析构方法
newisa.has_cxx_dtor = hasCxxDtor;
//把cls信息赋值给shiftcls
newisa.shiftcls = (uintptr_t)cls >> 3;
可以看到关键点是
构造newisa
这个对象并赋值给isa
,newisa的类型是isa_t,下面我们来看看它到底是如何实现的。
二、isa_t结构分析
继续跟踪源码,看一下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_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)
各成员字段说明如下:
成员 | 说明 |
---|---|
nonpointer |
表示是否对 isa 指针开启指针优化 0:纯isa指针,1:不止是类对象地址,isa 中包含了类信息、对象的引用计数等 |
has_assoc |
关联对象标志位,0没有,1存在 |
has_cxx_dtor |
该对象是否有 C++ 或者 Objc 的析构器,如果有析构函数,则需要做析构逻辑, 如果没有,则可以更快的释放对象 |
shiftcls |
存储类指针的值。开启指针优化的情况下,在 arm64 架构中用33 位用来存储类指针,x86 下用44 位 |
magic |
用于调试器判断当前对象是真的对象还是没有初始化的空间 |
weakly_referenced |
标志对象是否被指向或者曾经指向一个 ARC 的弱变量, |
deallocating |
标志对象是否正在释放内存 |
has_sidetable_rc |
当对象引用技术大于 10 时,则需要借用该变量存储进位 |
isa_t其实是一个
联合体位域
的结构,其关键的类信息存储在shiftcls中,其他都是一些辅助信息。既然关键的类信息都存储在shiftcls中,如何证明呢,下面我们通过获取类信息的流程来佐证。
三、获取类信息过程
平常我们在获取类信息时一般通过object_getClass方法,来看一下object_getClass这个方法的调用过程:
Class object_getClass(id obj)
{
//调用对象的getIsa方法
if (obj) return obj->getIsa();
else return Nil;
}
inline Class
objc_object::getIsa()
{
//自定义类都是非TaggedPointer类型的,所以直接调用ISA()方法
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());
//上面已经分析过,此处SUPPORT_INDEXED_ISA为0
#if SUPPORT_INDEXED_ISA
if (isa.nonpointer) {
uintptr_t slot = isa.indexcls;
return classForIndex((unsigned)slot);
}
return (Class)isa.bits;
#else
//所以最后获取类信息就是取bits & ISA_MASK
return (Class)(isa.bits & ISA_MASK);
#endif
}
# define ISA_MASK 0x00007ffffffffff8ULL
可以看到最后获取类信息就是取对象的bits & ISA_MASK的值,其实就是shiftcls中存储的信息,与上ISA_MASK(0x00007ffffffffff8ULL)
就是为了把低3位和高17位置0,写个简单的demo看一下:
这里补充几个知识点:
x/4gx
打印4个8字节内存信息
p/x
以16进制打印
p/t
以2进制打印
//LGPerson *p = [LGPerson alloc];
(lldb) p/x p
(LGPerson *) $21 = 0x000000010070c920
(lldb) p/t p
(LGPerson *) $22 = 0b0000000000000000000000000000000100000000011100001100100100100000
(lldb) x/4gx p
0x10070c920: 0x001d800100003275 0x0000000000000000
0x10070c930: 0x0000000000000000 0x0000000000000000
(lldb) p/t 0x001d800100003275
(long) $24 = 0b0000000000011101100000000000000100000000000000000011001001110101
(lldb) p/t 0x00007ffffffffff8ULL
(unsigned long long) $25 = 0b0000000000000000011111111111111111111111111111111111111111111000
(lldb) p/x 0x001d800100003275 & 0x00007ffffffffff8ULL
(unsigned long long) $26 = 0x0000000100003270
(lldb) p 0x0000000100003270
(long) $27 = 4294980208
(lldb) po 0x0000000100003270
LGPerson
看完上面的整个流程后,大家可能还有一个疑问:为什么在赋值shiftcls的时候要把cls右移3位
,而不是直接把cls的地址给它?
原因很简单:类其实是原类的实例对象
,它的真实的信息也是存储在8字节的4-47位上的,右移3位才能给把这44位信息一一对应的赋值给shiftcls,而在获取类信息时把isa & 0x00007ffffffffff8ULL
也是为了把低3位以及高17位
(注意是在mac上)置0保留中间44
位。
最后
上面的分析流程中也提到了对象、类、以及原类之间是存在一定关系的,通过上面的分析证明了对象和类之间关联性,但它们和原类之间有什么关系呢,这个问题就留待下次分析吧~
未完待续...
网友评论