iOS-isa

637 阅读6分钟

周边信息:

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是一个联合体其中定义了两个成员clsbits和一个结构体位域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中包含了类对象地址、类信息、对象的引用计数等10
has_assoc是否有关联对象 0:没有 1:存在11
has_cxx_dtor该对象是否有C++或者Objc的析构器 如果有析构函数则需要做析构逻辑 如果没有则可以更快的释放对象12
shiftcls存储类指针的值。开启指针优化的情况下,在arm64架构中有 33 位用来存储类指针333~35
magic用于调试器判断当前对象是真的对象还是没有初始化的空间536~40
weakly_referenced是否有弱引用 0:没有 1:存在141
deallocating是否正在释放内存 0:不是 1:是142
has_sidetable_rc是否需要用到外挂引用计数,当对象引用技术大于 10 则需要借用该变量存储进位143
extra_rc该对象的引用计数值,实际上是引用计数值减 1。如果对象的引用计数为10,那么 extra_rc 为 9。如果引用计数大于 10 则需要使用 has_sidetable_rc1944~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

官方流程图如下