周边信息:
1、结构体
结构体是C语言中一种重要的数据类型,该数据类型由一组称为成员(或称为域,或称为元素)的不同数据组成,其中每个成员可以具有不同的类型。结构体通常用来表示类型不同但是又相关的若干数据。
2、联合体
“联合”与“结构”有一些相似之处。但两者有本质上的不同。在结构中各成员有各自的内存空间,一个结构体变量的总长度大于等于各成员长度之和。而在“联合”中,各成员共享一段内存空间,一个联合变量的长度等于各成员中最长的长度。
3、位域
有些信息在存储时,并不需要占用一个完整的字节,而只需占几个或一个二进制位。例如在存放一个开关量时,只有0和1两种状态,用一位二进位即可。为了节省存储空间,并使处理简便。
资料准备:
1、objc源码下载opensource.apple.com/tarballs/ob…
源码分析:
1、isa的本质联合体
2、ISA_BITFIELD位域
3、初始化核心方法
isa的本质联合体
当多个数据需要共享内存或者多个数据每次只取其一时,可以利用联合体
struct objc_object {
private:
isa_t isa;
public:
// ISA() assumes this is NOT a tagged pointer object
Class ISA(bool authenticated = false);
// rawISA() assumes this is NOT a tagged pointer object or a non pointer ISA
Class rawISA();
// getIsa() allows this to be a tagged pointer object
Class getIsa();
uintptr_t isaBits() const;
。。。。。。
}
#include "isa.h"
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是isa_t类型,而isa_t是一个联合体其中定义了两个成员cls和bits和一个结构体位域ISA_BITFIELD(用来存放类信息和其他信息)
ISA_BITFIELD位域
# 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 unused : 1; \
uintptr_t has_sidetable_rc : 1; \
uintptr_t extra_rc : 8
| 参数名 | 作用 | 大小 | 所在位置 |
|---|---|---|---|
| nonpointer | 是否对isa指针开启指针优化 0:纯isa指针只包含类对象地址 1:isa中包含了类对象地址、类信息、对象的引用计数等 | 1 | 0 |
| has_assoc | 是否有关联对象 0:没有 1:存在 | 1 | 1 |
| has_cxx_dtor | 该对象是否有C++或者Objc的析构器 如果有析构函数则需要做析构逻辑 如果没有则可以更快的释放对象 | 1 | 2 |
| shiftcls | 存储类指针的值。开启指针优化的情况下,在arm64架构中有 33 位用来存储类指针 | 33 | 3~35 |
| magic | 用于调试器判断当前对象是真的对象还是没有初始化的空间 | 5 | 36~40 |
| weakly_referenced | 是否有弱引用 0:没有 1:存在 | 1 | 41 |
| deallocating | 是否正在释放内存 0:不是 1:是 | 1 | 42 |
| has_sidetable_rc | 是否需要用到外挂引用计数,当对象引用技术大于 10 则需要借用该变量存储进位 | 1 | 43 |
| extra_rc | 该对象的引用计数值,实际上是引用计数值减 1。如果对象的引用计数为10,那么 extra_rc 为 9。如果引用计数大于 10 则需要使用 has_sidetable_rc | 19 | 44~63 |
isa的存储分布如下图:
初始化核心方法
1、初始化(newisa)
2、成员赋值(newisa.bits = ISA_MAGIC_VALUE)
3、类关联(newisa.shiftcls = (uintptr_t)cls >> 3)
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;
}
}
newisa.shiftcls = (uintptr_t)cls >> 3;
为什么需要右移3位?
主要是由于shiftcls处于isa指针地址的中间部分,前面还有3个位域,为了不影响前面的3个位域的数据,需要右移将其抹零。
isa指针与类关联的验证:
一、通过查看object_getClass的源码
object_getClass -> getIsa -> ISA
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);
#endif
}
#if __ARM_ARCH_7K__ >= 2 || (__arm64__ && !__LP64__)
# define SUPPORT_INDEXED_ISA 1
#else
# define SUPPORT_INDEXED_ISA 0
#endif
通过上面可以看出类的获取其实就是isa.bits和ISA_MASK的与运算
ISA_MASK
define ISA_MASK 0x0000000ffffffff8ULL
isa.bits又是什么呢?见👇
为什么要与运算?因为isa是联合体,第3-35位用来存储class信息,isa需要进行一次位运算,才能计算出存储class真实地址,相当于只读取3-35位数据。
二、通过位运算
通过x/4gx obj得到obj的存储信息,当前类的信息存储在isa指针中,且isa中的shiftcls此时占44位(macOS环境为例__x86_64__)想要读取中间的44位类信息,就需要经过位运算
1、将右移3位,和左边除去44位以外的部分都抹零,其相对位置是不变的。
2、需要左移20位,才能将高17位抹零。
3、将结果右移17位,回到最初的位置。
流程如下
isa指向:
isa与对象的关系验证流程
一、通过位运算
二、通过日志
/// 实例对象
Persion *obj1 = [[Persion alloc]init];
/// 类对象
Class class = object_getClass(obj1);
/// 元类
Class metaClass = object_getClass(class);
/// 根元类
Class rootMetaClass = object_getClass(metaClass);
/// 根根元类
Class rootRootMetaClass = object_getClass(rootMetaClass);
NSLog(@"实例对象 == %@ %p",obj1,obj1);
NSLog(@"类对象 == %@ %p",class,class);
NSLog(@"父类对象 == %@ %p",obj1.superclass,obj1.superclass);
NSLog(@"元类 == %@ %p",metaClass,metaClass);
NSLog(@"元类父类对象 == %@ %p",metaClass.superclass,metaClass.superclass);
NSLog(@"根元类 == %@ %p",rootMetaClass,rootMetaClass);
NSLog(@"根元类父类对象 == %@ %p",rootMetaClass.superclass,rootMetaClass.superclass);
NSLog(@"根根元类 == %@ %p",rootRootMetaClass,rootRootMetaClass);
/// 实例对象
NSObject *obj2 = [[NSObject alloc]init];
NSLog(@"NSObject的父类 == %@ %p",obj2.superclass,obj2.superclass);
日志输出如下
实例对象 == <Persion: 0x281515560> 0x281515560
类对象 == Persion 0x10074d858
父类对象 == NSObject 0x1da65de08
元类 == Persion 0x10074d830
元类父类对象 == NSObject 0x1da65dde0
根元类 == NSObject 0x1da65dde0
根元类父类对象 == NSObject 0x1da65de08
根根元类 == NSObject 0x1da65dde0
NSObject的父类 == (null) 0x0
通过上面我们可以得出如下结论
isa走位链
1、对象的isa指向其类对象
2、类对象的isa指向其元类
3、元类的isa指向根元类
4、根元类的isa指向自己
Class继承链
1、子类继承父类最终都继承NSObject
2、元类继承父元类最终都继承根元类NSObject MetaClass
3、NSObject的父类指向nil
4、根元类NSObject MetaClass的父类指向根类NSObjec Class
万物都源于NSObject
官方流程图如下