isa详解

作者: 冼同学 | 来源:发表于2021-06-16 19:02 被阅读0次

    前言

    通过上一篇文章《iOS对象的本质》可以知道对象在底层被编译成结构体,其中第一个属性就是对象的isa。那么什么是isa呢?isa的包含哪些信息呢?isa的结构怎么的呢?它跟对象又是怎么联系在一起的?带着种种的问题我们往下走吧!(#.#)

    准备工作

    联合体(union)

    在开发过程中常用到一种结构体类型 struct,有一种和结构体比较相似的结构,叫共用体,也称联合体。代码如下:

    // 联合体 : 互斥
    union myPersion {
        short       money;  //2
        int         age;   //4
        double      height ;  //8
    };
    int main(int argc, const char * argv[]) {
        @autoreleasepool {
        union myPersion    persion;
        persion.money = 1;  
        NSLog(@"money=%d---age=%d---height=%f",persion.money,persion.age,persion.height);
        persion.age  = 18;
        NSLog(@"money=%d---age=%d---height=%f",persion.money,persion.age,persion.height);
        persion.height = 170.0;
        NSLog(@"money=%d---age=%d---height=%f",persion.money,persion.age,persion.height);
        NSLog(@"persion联合体的大小=%ld",sizeof(persion));    
    }
    

    运行结果:

    2021-06-16 15:39:03.030731+0800 001-联合体位域[66996:1423574] money=1---age=1---height=0.000000
    2021-06-16 15:39:50.484786+0800 001-联合体位域[66996:1423574] money=18---age=18---height=0.000000
    2021-06-16 15:40:14.492142+0800 001-联合体位域[66996:1423574] money=0---age=0---height=170.000000
    2021-06-16 15:40:54.421131+0800 001-联合体位域[66996:1423574] persion联合体的大小=8
    

    得出结论:

    • 联合体可以定义多个不同类型的成员,联合体的内存大小由其中最大的成员的大小决定
    • 联合体中修改其中的某个变量会覆盖其他变量的值或者取代其他的值(union内存块大小够之前变量存放的时候会覆盖,不够就取代)
    • 联合体所有的变量公用一块内存,变量之间 互斥
      与结构体(struct)相比;联合体(union)的优缺点:
    • 结构体(struct)中所有变量是“共存”的——优点是“有容乃大”, 全面;缺点是struct内存空间的分配是粗放的,不管用不用,全分配。
    • 联合体(union)中是各变量是“互斥”的——缺点就是不够“包容”; 但优点是内存使用更为精细灵活,也节省了内存空间.

    位域(Bit field)

    在开发过程中为了更加的合理利用内存,有些信息在存储时,并不需要占用一个完整的字节,而只需占几个或一个二进制位。例如BOOL值在存放只有一个只有01两种状态成员, 用一位二进位即可。分析代码如下:

    // 4 位
    // 1 字节 3倍浪费
    struct LGCar1 {
        BOOL front; // 0 1
        BOOL back;
        BOOL left;
        BOOL right;
    };
    // 位域
    // 互斥
    struct LGCar2 {
        BOOL front: 1;
        BOOL back : 1;
        BOOL left : 1;
        BOOL right: 1;
    };
    int main(int argc, char * argv[]) {
      
        @autoreleasepool {
            
        struct OldCar oldCar;
        struct NewCar newCar;
        NSLog(@"----%lu----%lu",sizeof(oldCar),sizeof(newCar));
            
        }
        return 0;
    }
    

    运行结果:

    2021-06-16 16:00:20.379723+0800 001-联合体位域[67528:1433973] ----4----1
    

    分析结果:
    好明显oldCar结构体的大小为4字节,newCard经过位运算大小为1字节。在newCard结构体1个字节的内存中,frontbackleftright都是占用1位,表示形式为:0000 1111。

    isa与类之间的关联流程

    《alloc底层原理探索》一文中已经结合alloc源码详细分析了一波isa指针与类是怎么样关联的。这里我就粗略的分析一下。
    alloc一个对象最核心的三个方法cls->instanceSize计算内存大小 ,(id)calloc(1, size)开辟内存返回地址指针,obj->initInstanceIsa初始化isa关联类。具体的流程总结为:alloc--> _objc_rootAlloc--> callAlloc--> _objc_rootAllocWithZone-->_class_createInstanceFromZone,断点在 obj->initInstanceIsa
    bj->initInstanceIsa源码流程分析:

    判断isa与类是否已经关联并初始化
    initInstanceIsa方法
    initIsa方法
    isa_t
    分析结果:
    isa_t其实就是一个联合体(union)sa_t有两个变量 一个是bits,一个是cls。通过上面分析联合体是互斥的,那就意味着初始化isa有两种方式:
    • bits被赋值,cls 没有值或者被覆盖
    • cls 被赋值,bits没有值或者被覆盖
      那么互斥一定存在一个变量嘛?变量都会被覆盖或者没有值嘛?联合体有没有具体的操作打破这种规律?哈哈哈,见下图:
    image.png
    我们尊敬的kc老师想都没想就给出他非常专业的答案:这个union进行了description表达

    isa的结构

    首先我们进去ISA_BITFIELD字段,看到的定义如下图:

    arm64isa结构
    x86isa结构
    isa各个字段在其64位的分布
    isa内部结构分布
    各变量的含义
    • nonpointer:表示是否对isa指针进行优化,0表示纯指针,1表示不止是类对象的地址,isa中包含了类信息、对象、引用计数等
    • has_assoc:关联对象标志位,0表示未关联,1表示关联
    • has_cxx_dtor:该对象是否C ++ 或者Objc的析构器,如果有析构函数,则需要做析构逻辑,没有,则释放对象
    • shiftcls:储存类指针的值,开启指针优化的情况下,在arm64架构中有33位用来存储类指针,x86_64架构中占44
    • magic:用于调试器判断当前对象是真的对象还是没有初始化的空间
      -weakly_referenced:指对象是否被指向或者曾经指向一个ARC的弱变量,没有弱引用的对象可以更快释放
    • deallocating:标志对象是否正在释放
    • has_sidetable_rc:当对象引用计数大于10时,则需要借用该变量存储进位
    • hextra_rc:表示该对象的引用计数值,实际上引用计数值减1,例如,如果对象的引用计数为10,那么extra_rc为9,如果大于10,就需要用到上面的has_sidetable_rc
      isa结构分析的总结:
    • 首先isa指针分为纯isa指针与非纯isa之整,nonpointer进行标记区分。
    • isa是联合体+位域的方式存储信息的。采用这种方式的有点就是节省大量内存。发挥了联合体的优势节省了比较多的内存空间。

    initIsa深入分析

    首先定义一个XXPersion,当newisa(0)调用完时候发现bits=0cls=nil且其他成员信息都为0,如下图所示:

    newisa(0)
    纯isa指针
    setClass逻辑
    验证位运算类信息地址
    分析:
    • shiftcls = 537307166 与上面的算法shiftcls=(uintptr_t)newCls >> 3得到的结果是一样的。
    • XXPerson的类地址>>3进行10进制转换赋值给shiftcls。此时isa已经关联XXPerson类 ,cls变量被覆盖 , cls = XXPerson
      补充:进行右移3位的目的主要是保持8字节的对齐,也就是说指针的地址只能是8的倍数,那么指针地址的后3位只能是0。同时虚拟内存中arm64架构的中间33位是类的地址,加上后面补0的三位,类的地址为36位,同理x86架构下的类地址为44+3=47

    isa位运算

    位运算操作
    位运算过程
    p/x 0x011d800100004561 >> 3
    (long) $38 = 0x0023b000200008ac
    (lldb) p/x 0x0023b000200008ac << 20
    (long) $39 = 0x000200008ac00000
    (lldb) p/x 0x000200008ac00000 >> 17
    (long) $40 = 0x0000000100004560
    

    位运算过程图像分析

    位运算过程图

    isa与ISA_MASK(掩码)进行&运算

    掩码解析类地址
    分析总结:
    p/x 0x011d800100004561 & 0x00007ffffffffff8ULL的结果是XXPersion,说明了isa已经关联了类。由图可知掩码(ISA_MASK)的二进制也是保留了中间的44位,其他位置为0,跟类在isa中的位置关系(shiftcls)是对应的。

    总结:通过对isa指针的深度学习,这个过程的确很难。但是收获也是非常多的。首先对自己开发的东西底层有更加深刻的理解,可以从底层的角度去深入发掘问题与研究,对个人技术的提升跟理解是有非常大的帮助,学到东西我感觉好兴奋,大家努力吧!!

    相关文章

      网友评论

          本文标题:isa详解

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