iOS - isa 和 类的关系探索

2020-09-14  本文已影响0人  malgee

我们使用的所有对象的都是继承至NSObject, 你是否会探索下NSObject到底是啥?
点进去可以看到出了方法之外只有一个变量 Class isa

@interface NSObject <NSObject> {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wobjc-interface-ivars"
    Class isa  OBJC_ISA_AVAILABILITY;
#pragma clang diagnostic pop
}

点击 Class可以可以发现是结构体objc_class

#if !OBJC_TYPES_DEFINED
/// An opaque type that represents an Objective-C class.
typedef struct objc_class *Class;

/// Represents an instance of a class.
struct objc_object {
    Class _Nonnull isa  OBJC_ISA_AVAILABILITY;
};

/// A pointer to an instance of a class.
typedef struct objc_object *id;
#endif

继续点击objc_class可以发现定义的方式, 发现继承至 objc_object, 通过上面定义的 objc_object结构体可以看到只有一个 isa变量

struct objc_class : objc_object {
    // Class ISA;   
    Class superclass;  
    cache_t cache;            // formerly cache pointer and vtable
    class_data_bits_t bits;   
    ....
}

通过objc4源码中找到定义结构体 objc_object, 可以发下也只有一个私有的变量 isa, 那么isa到底是什么呢?或者说 干了什么

struct objc_object {
private:
    isa_t isa;

public:
    // ISA() assumes this is NOT a tagged pointer object
    Class ISA();

    // 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();

    . . .
}

接续查找 isa_t发现定义 isa类型的结构是一个联合体(union 也称为共用体)

这里补充下知识点 结构体 和 联合体

构造数据类型的方式分为两种方式:

1. 结构体

结构体是指把不同的数据组合成一个整体,其变量是共存的,变量不管是否使用,都会分配内存。

【缺点】:所有属性都分配内存,比较浪费内存,假设有4个int成员,一共分配了16字节的内存,但是在使用时,你只使用了4字节,剩余的12字节就是属于内存的浪费

【优点】:存储容量较大,包容性强,且成员之间不会相互影响

2. 联合体

联合体也是由不同的数据类型组成,但其变量是互斥的,所有的成员共占一段内存。而且共用体采用了内存覆盖技术,同一时刻只能保存一个成员的值,如果对新的成员赋值,就会将原来成员的值覆盖掉

【缺点】:包容性弱,

【优点】:所有成员共用一段内存,使内存的使用更为精细灵活,同时也节省了内存空间

两者的区别

isa 的类型 isa_t

通过联合体 (union)定义的 isa_t类型, 其中 clsbits是互斥的,可以通过前面分析alloc & init探索 中查看具体赋值。 其中 cls存储具体的类, bits存放类的具体信息。

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_t类型使用联合体的原因也是基于内存优化的考虑,这里的内存优化是指在isa指针中通过char + 位域(即二进制中每一位均可表示不同的信息)的原理实现。通常来说, isa指针占用的内存大小是8字节,即64位,已经足够存储很多的信息了,这样可以极大的节省内存,以提高性能。

bits存放的类的更多信息可以通过 宏定义 ISA_BITFIELD中查看

---------------  arm64 架构 ----------------

# 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)

---------------  x86_64 架构 ----------------
# 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)

image.png
image.png
注:上面分析的是在arm64架构下的shiftcls占33位,在x86_64下构下shiftcls占44位

isa 与 类 的关联

clsisa 关联原理就是isa指针中的shiftcls位域中存储了类信息,其中initInstanceIsa的过程是将 calloc 指针 和当前的 类cls 关联起来,有以下几种验证方式:

【方式一】通过initIsa函数中的newisa.shiftcls = (uintptr_t)cls >> 3验证

【方式二】通过isa指针地址与ISA_MSAK 的值 & 来验证

【方式三】通过runtime的方法object_getClass验证

【方式四】通过位运算验证

方式一: 通过initIsa函数初始化赋值
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;
    }
}

例如自定义类MGPerson初始化是也会调用 initIsa ()函数,接下来分析 isa的关系

image.png

在248行给isashiftcls赋值MGPerson类的,这里就是将 cls类 和 isa指针通过shiftcls位域关联,存储着类的信息

方式二: 通过 isa & ISA_MSAK
image.png
方式三:通过 object_getClass 获取类和isa 关联

通过查看object_getClass的源码实现,同样可以验证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;
}

image.png

执行到174行会发现和方式二的原理一样,

方式四:通过位运算
image.png
上一篇下一篇

猜你喜欢

热点阅读