美文网首页
Objective-C isa指针探秘

Objective-C isa指针探秘

作者: milawoai | 来源:发表于2020-09-13 12:23 被阅读0次

    稍微精深一点的IOS开发都听说过isa指针。它在OC的类中起到了指示自身类型的作用,是runtime实现的基础。那么isa指针到底是如何实现的呢,让我们从源码的层面进行分析。

    NSObject -> Class -> objc_class -> objc_object

    新建一个最简单的空类:

    @interface Person : NSObject
    @end
    
    @implementation Person
    @end
    

    command点击NSObejct,我们可以看到cls的实现:

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

    那么这个Class又是什么数据呢?

    typedef struct objc_class *Class;
    

    我们可以看到Class实际上是objc_class结构体的指针。而objc_class,就是OC类的元类

    objc_class在objc源码中有三处定义:

    // in runtime.h
    struct objc_class {
        Class _Nonnull isa  OBJC_ISA_AVAILABILITY;
        .....
    } OBJC2_UNAVAILABLE;
    
    
    #if __OBJC2__
    #include "objc-runtime-new.h"
    #else
    #include "objc-runtime-old.h"
    #endif
    
    // in objc-runtime-old.h
    struct objc_class : objc_object {}
    
    // in objc-runtime-new.h
    struct objc_class : objc_object {}
    

    我们当前使用的版本多是 objc2,所以起作用的是 最后一个 objc-runtime-new.h中定义的objc_classobjc_class则继承了objc_object

    struct objc_class : objc_object 
    

    objc_object同样在两个位置有定义:

    in objc.h
    
    #if !OBJC_TYPES_DEFINED
    ....
    struct objc_object {
        Class _Nonnull isa  OBJC_ISA_AVAILABILITY;
    };
    #endif
    
    
    in objc-private.h
    
    #ifdef _OBJC_OBJC_H_
    #error include objc-private.h before other headers
    #endif
    
    #define OBJC_TYPES_DEFINED 1
    
    struct objc_object {
    private:
        isa_t isa;
    .....
    }
    

    根据objc-private.h中的宏定义我们可以看到,起作用的实际上是objc-private.h中的定义。

    对一个继承了NSObject的类而言,isa最后能定位到objc_object中。而用来存储类信息的,是objc_objectisa_t isa;

    isa_t

    ** isa_t是一个union**聚合体。聚合体和结构体最大的不同是它的内存存储方式。对聚合体而言,所有的成员变量的起始地址相同,对每一个成员的修改都会影响所有的变量。这样做最大的好处就是可以节约内存空间。但是也会导致每次可用的变量只有一个。

    union isa_t {
        isa_t() { }
        isa_t(uintptr_t value) : bits(value) { }
    
        Class cls; // 初始化是不会使用的
        uintptr_t bits;
    #if defined(ISA_BITFIELD)
        struct {
            ISA_BITFIELD;  // 这才是关键
        };
    #endif
    };
    

    isa_t 是一个联合体,这里所占空间为 8字节,共64位 ,内存布局从低位到高位情况如下图:

    
        struct {
            ISA_BITFIELD;  // 这才是关键
        };
    
    #   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
    

    注意 在 struct 的变量A后面加 :(数字n), 是指定该变量A在内存中占用 n 位。

    • nonpointer(存储在第0字节) : 是否为优化isa标志。0代表是优化前的isa,一个纯指向类或元类的指针;1表示优化后的isa,不止是一个指针,isa中包含类信息、对象的引用计数等。现在基本上都是优化后的isa。

    • has_assoc (存储在第1个字节): 关联对象标志位。对象含有或者曾经含有关联引用,0表示没有,1表示有,没有关联引用的可以更快地释放内存。

    • has_cxx_dtor(存储在第2个字节): 析构函数标志位,如果有析构函数,则需进行析构逻辑,如果没有,则可以更快速地释放对象。

    • shiftcls :(存储在第3-35字节)存储类的指针,其实就是优化之前 isa 指向的内容。在arm64架构中有33位用来存储类指针。x86_64架构有44位。

    • magic(存储在第36-41字节):判断对象是否初始化完成, 是调试器判断当前对象是真的对象还是没有初始化的空间。

    • weakly_referenced(存储在第42字节):对象被指向或者曾经指向一个 ARC 的弱变量,没有弱引用的对象可以更快释放(dealloc的底层代码有体现)。

    • deallocating(存储在第43字节):标志对象是否正在释放内存。

    • has_sidetable_rc(存储在第44字节):判断该对象的引用计数是否过大,如果过大则需要其他散列表来进行存储。

    • extra_rc(存储在第45-63字节。):存放该对象的引用计数值减1后的结果。对象的引用计数超过 1,会存在这个里面,如果引用计数为 10,extra_rc 的值就为 9。

    isa的初始化

    对OC类而言,isa的初始化是在 alloc中完成的。具体来说是在

    static ALWAYS_INLINE id
    _class_createInstanceFromZone(Class cls,...) {
    
    ...
    id obj; // id 是objc_object的指针
    
        // 3: ?
        if (!zone && fast) {
            obj->initInstanceIsa(cls, hasCxxDtor);
        } else {
            obj->initIsa(cls);
        }
    ...
    }
    

    initInstanceIsainitIsa最后都会统一到一个函数里:

    (去除一些宏定义,断言以及条件判断等,我们直接将代码减少到它执行的代码)

    inline void 
    objc_object::initIsa(Class cls, bool nonpointer, bool hasCxxDtor)  {
              isa_t newisa(0);
    
            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;
    
            isa = newisa;
    }
    
    1. 首先对整个bits进行赋值,传入 ISA_MAGIC_VALUE ,在arm64架构下,该值为
    #   define ISA_MAGIC_VALUE 0x000001a000000001ULL
    

    将该值转换为2进制

    image.png

    对应 isa_t 中内存布局的位置,可以看出对 bits 赋值 就是对 nonpinter 和 magic 赋值的过程。

    2.其次对has_cxx_dtor赋值。

    1. 最后对shifcls赋值

    shiftcls详解

    shiftcls 里面存放着类的指针。众所周知指针是64位的,那么为什么可以放置在33位或或者44位的shiftcls中呢?在需要指针时又如何还原呢?

    首先我们看下shiftcls的赋值:

    inline void 
    objc_object::initIsa(Class cls, bool nonpointer, bool hasCxxDtor) 
    { 
    ....
            isa_t newisa(0);
    ....
            newisa.shiftcls = (uintptr_t)cls >> 3;
    ....
            isa = newisa;
        }
    }
    

    首先我们将cls的尾部三位去掉,这是因为对类而言,它至少包含一个isa指针,那么它的长度一定是8的整数倍。这里我们可以使用runtime计算下Person的长度来认证下:

    #import <objc/runtime.h>
    size_t insSize = class_getInstanceSize([Person class]);
    NSLog(@"PP Size:%zd",insSize);
    

    然后,我们会看到源码中shiftcls的宏定义后面有一个数据 MACH_VM_MAX_ADDRESS:

     uintptr_t shiftcls          : 33; /*MACH_VM_MAX_ADDRESS 0x1000000000*/ \
    

    这是最大虚拟寻址空间,也就是内存空间能够分配的最大地址。以ARM32为例,它最大的虚拟寻址空间为 0x10,0000,0000。而33 + 3 (右移的三位) = 36 = 4 * 9。正好是虚拟寻址空间的位数 - 1。正好可以覆盖虚拟寻址空间。也就是说64位的数据中,前面64 - 36 = 18位是无用的。

    因此,只要存储64位指针中间的33位就可以表示了。只要在使用的时候与上mask:0x0000000ffffffff8,就可以得到对应的类指针。

    相关文章

      网友评论

          本文标题:Objective-C isa指针探秘

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