美文网首页
OC 对象原理探索(三):对象的本质 & isa

OC 对象原理探索(三):对象的本质 & isa

作者: SpringSunLcy | 来源:发表于2021-07-16 15:55 被阅读0次

    1. 联合体、位域

    1.1 结构体

    先看下面的代码:

    struct SSLCar {
        BOOL front;
        BOOL back;
        BOOL left;
        BOOL right;
    }sslCar;
    
    NSLog(@"sslCar:%lu",sizeof(sslCar));
    
    打印结果:sslCar:4
    

    我们看到一个SSLCar结构体是4个字节,也就是32位(0000 0000 0000 0000 0000 0000 0000 0000),而SSLCar结构体中4BOOL值只需要4位(1111)就可以存储,这造成了非常大的空间浪费。

    1.2 位域

    通过位域指定每个成员变量占1位:

    struct SSLCar2 {
        BOOL front: 1;
        BOOL back : 1;
        BOOL left : 1;
        BOOL right: 1;
    }sslCar2;
    
    NSLog(@"sslCar2:%lu",sizeof(sslCar2));
    
    打印结果:sslCar2:1
    

    此时内存空间已经得到了不错的优化。

    1.3 联合体

    创建联合体:

    union SSLPerson {
        char *name;
        int  age;
    };
    

    为联合体赋值,并打印:

    image

    通过打印结果,发现联合体只有后赋值的变量才有值。

    1.4 结构体和联合体的区别

    结构体(struct)中所有变量是“共存”的——优点是“有容乃大”,全面;缺点struct内存空间的分配是粗放的,不管用不用,全分配。

    联合体(union)中是各变量是“互斥”的——缺点就是不够“包容”; 但优点是内存使用更为精细灵活,也节省了内存空间。

    2. 对象本质

    main.m中添加如下代码:

    @interface SSLPerson : NSObject
    
    @property (nonatomic, copy) NSString *sslName;
    
    @end
    
    @implementation SSLPerson
    
    @end
    
    
    int main(int argc, const char * argv[]) {
        @autoreleasepool {
            
        }
        return 0;
    }
    

    通过clang命令编译main.m(更多clang信息点这里):

    clang -rewrite-objc main.m -o main.cpp
    

    得到main.cpp文件,在main.cpp中我们可以看到有关对象的信息:

    typedef struct objc_class *Class; // 7658行
    
    struct objc_object { // 7661 行
        Class _Nonnull isa __attribute__((deprecated));
    };
    
    typedef struct objc_object *id; // 7666行
    
    typedef struct objc_object SSLPerson; // 111788行
    
    struct SSLPerson_IMPL { // 111793行
        struct NSObject_IMPL NSObject_IVARS;
        NSString *_sslName;
    };
    

    由此我们可以得出,对象是一个objc_object类型的结构体,内部有一个Class类型的isa指针。

    打开源码,查看objc_object

    struct objc_object {
    private:
        isa_t isa;
        ...
    }
    

    3. isa 结构分析

    我们在 OC 对象原理探索(一) 中,提到过obj->initIsa(cls),它的作用是将指针和类进行关联,下面我们用源码进行详细的分析。

    initIsa函数:

    inline void 
    objc_object::initIsa(Class cls)
    {
        initIsa(cls, false, false);
    }
    
    objc_object::initIsa(Class cls, bool nonpointer, bool hasCxxDtor) {
        ASSERT(!isTaggedPointer()); 
        
        isa_t newisa(0); // isa初始化
    
        if (!nonpointer) {
            newisa.setClass(cls, this);//如果是纯指针 isa被直接cls赋值
        } else { 
           ASSERT(!DisableNonpointerIsa);
           ASSERT(!cls->instancesRequireRawIsa());
    #if SUPPORT_INDEXED_ISA
            ASSERT(cls->classArrayIndex() > 0);
            newisa.bits = ISA_INDEX_MAGIC_VALUE;
            newisa.has_cxx_dtor = hasCxxDtor;
            newisa.indexcls = (uintptr_t)cls->classArrayIndex();
    #else   
            newisa.bits = ISA_MAGIC_VALUE;
    #   if ISA_HAS_CXX_DTOR_BIT
            newisa.has_cxx_dtor = hasCxxDtor;
    #   endif
            newisa.setClass(cls, this);
    #endif
            newisa.extra_rc = 1;
        }
        isa = newisa;
    }
    

    点击查看isa_t;

    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_BITFIELD

    # if __arm64__
    #   define ISA_MASK        0x0000000ffffffff8ULL
    #   define ISA_MAGIC_MASK  0x000003f000000001ULL
    #   define ISA_MAGIC_VALUE 0x000001a000000001ULL
    #   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
    #   define RC_ONE   (1ULL<<45)
    #   define RC_HALF  (1ULL<<18)
    
    # elif __x86_64__
    #   define ISA_MASK        0x00007ffffffffff8ULL
    #   define ISA_MAGIC_MASK  0x001f800000000001ULL
    #   define ISA_MAGIC_VALUE 0x001d800000000001ULL
    #   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 deallocating      : 1;                                         \
          uintptr_t has_sidetable_rc  : 1;                                         \
          uintptr_t extra_rc          : 8
    #   define RC_ONE   (1ULL<<56)
    #   define RC_HALF  (1ULL<<7)
    

    看一下isa的内存分布图:

    image
    • nonpointer:表示是否对isa指针开启指针优化。0:纯isa指针,1:不止是类对象地址,isa中包含了类信息、对象的引用计数等。
    • has_assoc:关联对象标志位,0没有,1存在。
    • has_cxx_dtor:该对象是否有C++或者Objc的析构器,如果有析构函数,则需要做析构逻辑, 如果没有,则可以更快的释放对象。
    • shiftcls:存储类指针的值。开启指针优化的情况下,在arm64架构中有33位用来存储类指针。
    • magic:用于调试器判断当前对象是真的对象还是没有初始化的空间。
    • weakly_referenced:标志对象是否被指向或者曾经指向一个ARC的弱变量,没有弱引用的对象可以更快释放。
    • deallocating:标志对象是否正在释放内存。
    • extra_rc:表示该对象的引用计数值,如果不够存储,has_sidetable_rc的值就会变为 1;
    • has_sidetable_rc:如果为1,代表引用计数过大无法存储在isa中,那么超出的引用计数会存储在SideTableRefCountMap中。所以,如果isanonpointer,则对象的引用计数存储在它的isa_textra_rc中以及SideTableRefCountMap中。

    总结:

    • isa分为nonpointer类型和非nonpointer类型。非nonpointer类型是一个纯指针,nonpointer类型还包含了引用计数、关联对象等信息。
    • isa采用联合体+位域的方式存储信息。因为在iOS中万物皆对象,对象中都用到了isa,这样可以大大的节省内存空间。

    4. isa 关联类

    先创建一个SSLperson类,初始化[SSLPerson alloc],在initIsa函数中断点调试:

    image.png

    如上图,创建了一个联合体newisa,里边的变量目前都是0,没有赋值。

    继续向下运行:

    image.png

    如上图,newisa.bits = ISA_MAGIC_VALUEISA_MAGIC_VALUE是一个宏
    =0x001d800000000001。被赋值的变量有bits=8303511812964353cls=0x001d800000000001nonpointer=1magic=59。转化成二进制表示:

    image.png
    • 0x001d800000000001=8303511812964353,它们的二进制表示相同。
    • nonpointer的二进制表示是1magic的二进制表示是111011,它们包含在bits的二进制表示中。

    断点进入setClass

    image.png
    • 这里,isaSSLPerson类进行了关联。
    • 目前是nonpointer,所以并不会给cls赋值。
    • SSLPerson的类地址>>3进行10进制转换,然后赋值给shiftcls
      • 为什么要右移3位呢,因为shiftclsisa中是从第四位开始存储的,这样isa & ISA_MASK才能得到准确的类地址

    继续向下运行:

    image.png
    image.png
    • shiftclsextra_rc也被成功赋值。
    • 这里我一直有个疑惑cls明明没有被赋值,为什么却是SSLPerson呢,而且联合体也应该会把cls覆盖掉才对,所以我后期有做了一个调试:
      • image.png
      • 打印了cls的值,它确实是整个isa的值,至于为什么显示SSLPerson,应该是系统做了处理。

    到此,isa关联的探索基本完成,以后如有更深入理解会持续更新。

    相关文章

      网友评论

          本文标题:OC 对象原理探索(三):对象的本质 & isa

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