美文网首页
iOS 对象isa指针的底层探索

iOS 对象isa指针的底层探索

作者: 无极战思 | 来源:发表于2021-06-23 18:07 被阅读0次

    引言

    众所周知,oc是面向对象编程,最重要的一个概念就是,最终OC中的类都要编译成c++的,那么OC重的类在c++的底层是怎么呈现的呢?
    打开#import<objc/objc.h>文件

     /// An opaque type that represents an Objective-C class.
    //一个不透明类型用于表示Objective-C类
     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;
    

    从上面代码可以看出,OC中的类(Class)编译后在c++中是以 struct objc_class呈现的,我们众所周知的NSObject类编译后在c++中是一个 struct objc_object,里面只有一个成员变量,即Class类型的变量isa
    在这里,我们也明白了为什么oc中的大部分对象可以用 id接收,因为id本身就是一个objc_object类型的结构体指针
    那么objc_objectobjc_class是什么关系呢?其实我们自定义(继承于NSObject)的类,编译成c++对应的就是objc_class

    查看源码runtime.h,发现结构体objc_class如下定义,这个是老版本OBJC1_(系统低于10.15)时候的定义

    struct objc_class {
       Class _Nonnull isa  OBJC_ISA_AVAILABILITY;
    
    #if !__OBJC2__
    Class _Nullable super_class                              OBJC2_UNAVAILABLE;
    const char * _Nonnull name                               OBJC2_UNAVAILABLE;
    long version                                             OBJC2_UNAVAILABLE;
    long info                                                OBJC2_UNAVAILABLE;
    long instance_size                                       OBJC2_UNAVAILABLE;
    struct objc_ivar_list * _Nullable ivars                  OBJC2_UNAVAILABLE;
    struct objc_method_list * _Nullable * _Nullable methodLists                    OBJC2_UNAVAILABLE;
    struct objc_cache * _Nonnull cache                       OBJC2_UNAVAILABLE;
    struct objc_protocol_list * _Nullable protocols          OBJC2_UNAVAILABLE;
    #endif
    
    } OBJC2_UNAVAILABLE;
    

    查看源码objc-runtime-new.h,结构体objc_class在新版本OBJC2_定义如下,isa是继承的父类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;    // class_rw_t * plus custom rr/alloc flags
    ...
    }
    

    下面我们重点研究isa,问题来了,想要研究isa,该从哪里入手?

    我们前面研究了对象alloc流程的底层源码,方法调用顺序为:alloc--->_objc_rootAlloc--->callAlloc--->_objc_rootAllocWithZone--->_class_createInstanceFromZone.

     static ALWAYS_INLINE id _class_createInstanceFromZone(Class cls, size_t extraBytes, void *zone,
                              int construct_flags = OBJECT_CONSTRUCT_NONE,
                              bool cxxConstruct = true,
                              size_t *outAllocatedSize = nil)
    {
     ASSERT(cls->isRealized());
    
    // Read class's info bits all at once for performance
    bool hasCxxCtor = cxxConstruct && cls->hasCxxCtor();
    bool hasCxxDtor = cls->hasCxxDtor();
    bool fast = cls->canAllocNonpointer();
    size_t size;
    
    size = cls->instanceSize(extraBytes); //计算对象内存大小
    if (outAllocatedSize) *outAllocatedSize = size;
    
    id obj;
    if (zone) {
        obj = (id)malloc_zone_calloc((malloc_zone_t *)zone, 1, size);
    } else {
        obj = (id)calloc(1, size); //calloc 分配堆内存
    }
    if (slowpath(!obj)) {
        if (construct_flags & OBJECT_CONSTRUCT_CALL_BADALLOC) {
            return _objc_callBadAllocHandler(cls);
        }
        return nil;
    }
    
    if (!zone && fast) {
        obj->initInstanceIsa(cls, hasCxxDtor);//初始化对象的isa
    } else {
        // Use raw pointer isa on the assumption that they might be
        // doing something weird with the zone or RR.
        obj->initIsa(cls);
    }
    
    if (fastpath(!hasCxxCtor)) {
        return obj;
    }
    
    construct_flags |= OBJECT_CONSTRUCT_FREE_ONFAILURE;
    return object_cxxConstructFromClass(obj, cls, construct_flags);
    }
    

    代码中我们可以清晰的看到:_class_createInstanceFromZone做了三件事情。

    • size = cls->instanceSize(extraBytes); //计算对象内存大小
    • obj = (id)calloc(1, size); //calloc 分配堆内存
    • obj->initIsa(cls);//初始化对象的isa

    因此,initIsa就顺利成章的成为了我们研究isa的切入点。isa的初始化代码如下:

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

    initIsa代码看出,该方法做了3件事。

    1.初始化一个isa_t类型的newisa
    2.newisa.bits位域的初始化
    3.将newisa赋值给isa,完成isa的初始化工作

    那么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_tunion(联合体),使用union也是基于内存优化,isa指针8字节,即64位,已经足够存储很多的信息了,由于union的特性互斥性(内存共用),极大得节省了内存
    提供了两个成员变量,clsbits,由union的互斥性知只能存在一种形式初始化,那么根据什么判断呢?

    image.png
    不难看出,根据nonpointer判断用哪种方式初始化。

    还提供了一个结构体定义的位域,用于存储类信息及其他信息,结构体的成员ISA_BITFIELD是一个宏定义。

    image.png
    # 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
    #     ...
    #   else
    #     define ISA_MASK        0x0000000ffffffff8ULL
    #     define ISA_MAGIC_MASK  0x000003f000000001ULL
    #     define ISA_MAGIC_VALUE 0x000001a000000001ULL
    #     define ISA_HAS_CXX_DTOR_BIT 1
    #     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
       #     define RC_ONE   (1ULL<<45)
      #     define RC_HALF  (1ULL<<18)
      #   endif
    
      # elif __x86_64__
      #   define ISA_MASK        0x00007ffffffffff8ULL
      #   define ISA_MAGIC_MASK  0x001f800000000001ULL
      #   define ISA_MAGIC_VALUE 0x001d800000000001ULL
      #   define ISA_HAS_CXX_DTOR_BIT 1
      #   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
     #   define RC_ONE   (1ULL<<56)
     #   define RC_HALF  (1ULL<<7)
    
    # else
     #   error unknown architecture for packed isa
     # endif
    

    解释: : 1;: 44;分别表示该成员占1位44位
    下面我们来看不同位域的作用:

    • nonpointer:是否开启指针优化0:纯isa指针只包含类对象地址1isa中包含了类对象地址、类信息、对象的引用计数等
      还得回到_class_createInstanceFromZone

        if (!zone && fast) {
        obj->initInstanceIsa(cls, hasCxxDtor);
        } else {
        obj->initIsa(cls);
         }
      

    obj->initInstanceIsa(cls, hasCxxDtor);调用的initIsanonpointer传的值为true
    obj->initIsa(cls);调用的initIsanonpointer传的值为false

    image.png

    nonpointer == NO时候,走if判断,即只对newisa的class设置。
    nonpointer == YES时候,走else判断,对newisa多项赋值。

    • has_assoc:是否设置过关联对象,如果没有,释放时会更快。
    • has_cxx_dtor:是否含有c++的析构函数。
    • shiftcls:存储着ClassMeta-Class对象的内存地址信息,是我们研究的重点(arm64:33位,x86_64:44位)
    • magic:判断当前对象是否进行了初始化
    • weakly_referenced:是否被弱引用指向过。
    • has_sidetable_rc:判断是否需要用sidetable去处理引用计数(extra_rc的大小影响整个变量)
    • extra_rc:里面存储的值是引用计数器减1

    这里重点提到的是shiftcls,那么shiftcls存的地址信息和我们的地址有什么关系呢?

    image.png

    从上面可以清晰的看到,shiftcls的地址 = WJPerson类地址 >> 3(向右移动了三位)得到的。

    相关文章

      网友评论

          本文标题:iOS 对象isa指针的底层探索

          本文链接:https://www.haomeiwen.com/subject/jyemyltx.html