iOS对象的本质 & ISA

698 阅读4分钟

引入对象的本质

通过查看底层代码,了解到对象创建的流程。下面是几个最主要的代码

size = cls->instanceSize(extraBytes);
obj = (id)calloc(1, size);
obj->initIsa(cls);

大致的意思是:

  1. 计算Class内存大小;
  2. 根据size开辟内存空间;
  3. 将obj和cls绑定在一起。 前两个步骤不难理解,但是initIsa是怎么将它们绑定到一起的呢。接下来进入initIsa()的源码探究一下。
inline void 
objc_object::initIsa(Class cls, bool nonpointer, UNUSED_WITHOUT_INDEXED_ISA_AND_DTOR_BIT bool hasCxxDtor)
{ 
    ASSERT(!isTaggedPointer()); 
    
    isa_t newisa(0);

    if (!nonpointer) {
        newisa.setClass(cls, this);
    } else {
        ASSERT(!DisableNonpointerIsa);
        ASSERT(!cls->instancesRequireRawIsa());


#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
#   if ISA_HAS_CXX_DTOR_BIT
        newisa.has_cxx_dtor = hasCxxDtor;
#   endif
        newisa.setClass(cls, this);
#endif
        newisa.extra_rc = 1;
    }

    // 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;
}

从上面的代码不难看出setClass(cls, this)是主要的步骤,其他的就是些判断和初始化赋值。这里同时引入了新的东西,那就是isa_t

isa_t

进入isa_t

union isa_t {
    isa_t() { }
    isa_t(uintptr_t value) : bits(value) { }

    uintptr_t bits;

private:
    // Accessing the class requires custom ptrauth operations, so
    // force clients to go through setClass/getClass by making this
    // private.
    Class cls;

public:
#if defined(ISA_BITFIELD)
    struct {
        ISA_BITFIELD;  // defined in isa.h
    };

    bool isDeallocating() {
        return extra_rc == 0 && has_sidetable_rc == 0;
    }
    void setDeallocating() {
        extra_rc = 0;
        has_sidetable_rc = 0;
    }
#endif

    void setClass(Class cls, objc_object *obj);
    Class getClass(bool authenticated);
    Class getDecodedClass(bool authenticated);
};

首先可以看到isa_t是union,union即是联合体,联合体类似结构体,其内的成员公用同一片空间,它的内存大小就是其内最大成员的大小,最大的区别就是联合体内的成员是互斥的,其优点是使内存的使用更加的精细灵活,节省内存空间。

另一个重要的成员就是ISA_BITFIELD,如下:

# if __arm64__
// ARM64 simulators have a larger address space, so use the ARM64e
// scheme even when simulators build for ARM64-not-e.
#   if __has_feature(ptrauth_calls) || TARGET_OS_SIMULATOR
#     define ISA_BITFIELD                                                      \
        uintptr_t nonpointer        : 1;                                       \
        uintptr_t has_assoc         : 1;                                       \
        uintptr_t weakly_referenced : 1;                                       \
        uintptr_t shiftcls_and_sig  : 52;                                      \
        uintptr_t has_sidetable_rc  : 1;                                       \
        uintptr_t extra_rc          : 8
#   else
#     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 unused            : 1;                                       \
        uintptr_t has_sidetable_rc  : 1;                                       \
        uintptr_t extra_rc          : 19
#   endif

# elif __x86_64__
#   define ISA_BITFIELD                                                        \
      uintptr_t nonpointer        : 1;  // 表示是否对 isa 指针开启指针优化 0:纯isa指针,1:不止是类对象地址,isa 中包含了类信息、对象的引用计数等                           \
      uintptr_t has_assoc         : 1;  // 关联对象标志位,0没有,1存在           \
      uintptr_t has_cxx_dtor      : 1;  // 该对象是否有 C++ 或者 Objc 的析构器,如果有析构函数,则需要做析构逻辑, 如果没有,则可以更快的释放对象                           \
      uintptr_t shiftcls          : 44; // 存储类指针的值。开启指针优化的情况下,在 arm64 架构中有 33 位用来存储类指针 /*MACH_VM_MAX_ADDRESS 0x7fffffe00000*/ \
      uintptr_t magic             : 6;  // 用于调试器判断当前对象是真的对象还是没有初始化的空间                                \
      uintptr_t weakly_referenced : 1;  // 指对象是否被指向或者曾经指向一个 ARC 的弱变量,
没有弱引用的对象可以更快释放。                                       \
      uintptr_t unused            : 1;  // 标志对象是否在被释放内存                                        \
      uintptr_t has_sidetable_rc  : 1;  // 当对象引用技数大于 10 时,则需要借用该变量存储进位,指是否需要使用 sidetable 来存储引用计数                                       \
      uintptr_t extra_rc          : 8   // 当表示该对象的引用计数值,实际上是引用计数值减 1, 例如,如果对象的引用计数为 10,那么 extra_rc 为 9。如果引用计数大于 10, 则需要使用到下面的 has_sidetable_rc

# else
#   error unknown architecture for packed isa
# endif

ISA_BITFIELD中可以看出使用位域存放了很多信息,并且按照系统进行了区分。

这里先引入位域的概念,位域是指:有些数据在存储时并不需要占用一个完整的字节,只需要占用一个或几个二进制位即可。例如开关只有通电和断电两种状态,用 0 和 1 表示足以,也就是用一个二进位。正是基于这种考虑,C语言又提供了一种叫做位域的数据结构。位域可以实现节省空间的目的。

位域的尺寸大小是根据每个成员实际占用的位数来计算(不用按照结构体三大法则进行补位),最终结果必须是其内最大成员大小的整数倍。

那么要怎么在ISA_BITFIELD中取出class信息呢,只需要用isa的数据与上ISA_MASK即可。 在Class getClass(bool authenticated);里也能看到class的获取方式。

inline Class
isa_t::getClass(MAYBE_UNUSED_AUTHENTICATED_PARAM bool authenticated) {
#if SUPPORT_INDEXED_ISA
    return cls;
#else

    uintptr_t clsbits = bits;

#   if __has_feature(ptrauth_calls)
#       if ISA_SIGNING_AUTH_MODE == ISA_SIGNING_AUTH
    // Most callers aren't security critical, so skip the
    // authentication unless they ask for it. Message sending and
    // cache filling are protected by the auth code in msgSend.
    if (authenticated) {
        // Mask off all bits besides the class pointer and signature.
        clsbits &= ISA_MASK;
        if (clsbits == 0)
            return Nil;
        clsbits = (uintptr_t)ptrauth_auth_data((void *)clsbits, ISA_SIGNING_KEY, ptrauth_blend_discriminator(this, ISA_SIGNING_DISCRIMINATOR));
    } else {
        // If not authenticating, strip using the precomputed class mask.
        clsbits &= objc_debug_isa_class_mask;
    }
#       else
    // If not authenticating, strip using the precomputed class mask.
    clsbits &= objc_debug_isa_class_mask;
#       endif

#   else
    clsbits &= ISA_MASK;
#   endif

    return (Class)clsbits;
#endif
}

总结

  1. 对象的本质就是isa,里面存储了对象的基本信息。
  2. isa使用是联合体位域,大大的节省的内存空间。