美文网首页
iOS-isa结构探一探

iOS-isa结构探一探

作者: 灰溜溜的小王子 | 来源:发表于2020-09-11 16:52 被阅读0次

    本文仅记录笔者的学习过程,只代表笔者个人的理解,如果有错的地方,欢迎各位指正!

    OC对象的本质

    对象继承与NSObject翻看Objc源码可以看到

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

    可以看到Class类型的isa继续跟进去可以看到

    typedef struct objc_class *Class;
    

    可以得治Class其实是个结构体,继续找objc_class结构体的定义

    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;
    

    从上面的源码中我们得知对象的本质是个结构体,且结构体中国呢的第一个参数是一个isa

    isa是什么

    这个isa也就是将要探索的内容。在笔者OC对象之alloc探索-源码探索的三种方式这篇博文中可以看到有这么一个初始化

    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);
        }
        if (slowpath(!obj)) {
            if (construct_flags & OBJECT_CONSTRUCT_CALL_BADALLOC) {
                return _objc_callBadAllocHandler(cls);
            }
            return nil;
        }
        //创建isa
        if (!zone && fast) {
            obj->initInstanceIsa(cls, hasCxxDtor);
        } 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);
    }
    

    其中有obj->initInstanceIsa(cls, hasCxxDtor)跟踪这个创建过程

    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;
        }
    }
    
    断点定位 image.png
    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指针占用的内存大小是8字节,即64位,已经足够存储很多的信息了,这样可以极大的节省内存,以提高性能。
    • isa_t这个粘合体中可以发现内部嵌套了结构体,结构体内部是哥宏定义ISA_BITFIELD
      image.png

    至此isa的结构清晰了,我们就来讲解一下,他每一个字段代表的含义吧:

    • nonpointer:表示是否对isa指针开启指针优化,0:纯isa指针,1:不⽌是类对象地址,isa 中包含了类信息、对象的引⽤计数等。
    • has_assoc:关联对象标志位,0没有,1存在。
    • has_cxx_dtor:该对象是否有 C++ 或者 Objc 的析构器,如果有析构函数,则需要做析构逻辑,如果没有,则可以更快的释放对象。
    • shiftcls:存储类指针的值。开启指针优化的情况下,在 arm64 架构中有 33 位⽤来存储类指针。
    • magic:⽤于调试器判断当前对象是真的对象还是没有初始化的空间。
    • weakly_referenced:志对象是否被指向或者曾经指向⼀个 ARC 的弱变量,没有弱引⽤的对象可以更快释放。
    • deallocating:标志对象是否正在释放内存。
    • has_sidetable_rc:当对象引⽤技术⼤于 10 时,则需要借⽤该变量存储进位。
    • extra_rc:当表示该对象的引⽤计数值,实际上是引⽤计数值减 1,例如,如果对象的引⽤计数为 10,那么 extra_rc 为 9。如果引⽤计数⼤于 10,则需要使⽤到上⾯的 has_sidetable_rc。
      image.png

    位域与联合体

    通过探索可以知道isa_t是一个联合体位域结构那么什么是粘合体位域?

    • 位域: 位域就是把一个字节中的二进位划分为几个不同的区域,并说明每个区域的位数。每个域有一个域名,允许在程序中按域名进行操作——这样就可以把几个不同的对象用一个字节的二进制位域来表示。位域是C语言一种数据结构
    • 使用位域的好处是:
      • 有些信息在存储时,并不需要占用一个完整的字节,而只需占几个或一个二进制位。例如在存放一个开关量时,只有0和1 两种状态,用一位二进位即可。这样节省存储空间,而且处理简便。 这样就可以把几个不同的对象用一个字节的二进制位域来表示。
      • 可以很方便的利用位域把一个变量给按位分解。比如只需要4个大小在0到3的随即数,就可以只rand()一次,然后每个位域取2个二进制位即可,省时省空间。

    位域的使用

    C语言中,位域的生命和结构体类似不同是:

    • 在声明时,位域成员必须是整形或枚举类型(通常是无符号类型)
    • 成员名的后面是一个冒号和一个整数,整数规定了成员所占用的位数
    • 位域不能是静态类型。不能使用&对位域做取地址运算,因此不存在位域的指针,编译器通常不支持位域的引用(reference)

    分别创建位域和结构体对比:

    struct Stuct {
        // (数据类型 元素);
        int  a;// 4字节 0 1 2 3
        char b;// 1字节 4。不够8字节补齐
    }Stuct1;
    
    struct Uni{
        // (数据类型 位域名: 位域长度);
        int a : 1;
        long b : 1;
    }Uni1;
    
    来看下效果: image.png
    image.png

    联合体

    • 联合体结构体的区别: 结构体中每个成员都会独立一块内存,相互不影响!而联合体所有成员公用一块内存!牵一而动全身!
    1.1 联合体特征
    union PPP {
        int a;      //4个字节
        short b;    //2个字节
        char c;     //1个字节
    } p;
    
    NSLog(@"union size: %lu - %lu",sizeof(p), sizeof(union PPP));
    结果:
    union size: 4 - 4
    
    
    union PPP {
        int a;      //4个字节
        short b;    //2个字节
        char c;     //1个字节
        long d;     //8个字节
    } p;
    结果
    union size: 8 - 8
    

    可见:联合体的内存是成员所需的最大内存那个。

    1.2 联合体特征
     p.a = 2;
    NSLog(@"%d---%d---%c---%d",p.a,p.b,p.c,p.d);//2---2---�---2
    p.b = 4;
    NSLog(@"%d---%d---%c---%d",p.a,p.b,p.c,p.d);//4---4---�---4
    p.c = 'c';
    NSLog(@"%d---%d---%c---%d",p.a,p.b,p.c,p.d);//99---99---c---99
    

    可见:每次改变联合体中的成员,其他成员会受到影响、即联合体成员之间是相互互斥的

    相关文章

      网友评论

          本文标题:iOS-isa结构探一探

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