美文网首页
ios的对象、类和isa分析以及LLDB调试

ios的对象、类和isa分析以及LLDB调试

作者: 正_文 | 来源:发表于2020-01-10 15:43 被阅读0次

    研究oc底层之前,先从苹果开源网站下载相应的代码

    1:objc_class、objc_object、id、isa

    通过代码先了解一下几个结构体的依赖关系,下面介绍isa

    typedef struct objc_class *Class;
    typedef struct objc_object *id;
    
    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;
    /* Use `Class` instead of `struct objc_class *` */
    
    
    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
    
        class_rw_t *data() { 
            return bits.data();
        }
        void setData(class_rw_t *newData) {
            bits.setData(newData);
        }
    //上面是objc_class部分代码
    
    struct objc_object {
    private:
        isa_t isa;
    
    public:
    
        // ISA() assumes this is NOT a tagged pointer object
        Class ISA();
    
        // getIsa() allows this to be a tagged pointer object
        Class getIsa();
    

    2:isa

    在arm64架构之前,isa仅是一个指针,保存着类对象(Class)元类对象(Meta-Class)的内存地址,在arm64架构之后,苹果对isa进行了优化,变成了一个isa_t类型的联合体(union)结构,同时使用位域来存储更多的信息

    查看isa可以通过object_getClass方法

    一个8字节指针在64位下 其实可以存储很多内容,我们可以优化内存,在不同的位上,放不同的东西! 在这我们还需要补充一下Struct与Union的区别:
    1.struct和union都是由多个不同的数据类型成员组成,但在任何同一时刻,union 中只存放了一个被选中的成员, 而struct的所有成员都存在。在struct中,各成员都占有自己的内存空间,它们是同时存在的。一个struct变量的总长度等于所有成员长度之和。在Union中,所有成员不能同时占用它的内存空间,它们不能同时存在。Union变量的长度等于最长的成员的长度
    2.对于union的不同成员赋值, 将会对其它成员重写, 原来成员的值就不存在了, 而对于struct的不同成员赋值是互不影响的.

    通过源码我们发现isa它是一个位域联合体,位域联合体是一个结构占8个字节,它的特性就是共用内存,或者说是互斥,比如说如果cls赋值了就不在对bits进行赋值.在isa_t联合体内使用宏ISA_BITFIELD定义了位域,我们进入位域内查看源码:

    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
    };
    
    /**
    我用的模拟器,所以只看`x86_64`
    */
    # 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)
    

    也就是说,我们之前熟知的OC对象的isa指针并不是直接指向类对象或者元类对象的内存地址,而是需要& ISA_MASK通过位运算才能获取类对象或者元类对象的地址.

    inline Class 
    objc_object::ISA() 
    {
        assert(!isTaggedPointer()); 
    #if SUPPORT_INDEXED_ISA
        if (isa.nonpointer) {
            uintptr_t slot = isa.indexcls;
            return classForIndex((unsigned)slot);
        }
        return (Class)isa.bits;
    #else
        //主要是这里,其他判断可以不关注
        //从64bit开始,需要经过一次位运算,才能计算出isa真实地址
        return (Class)(isa.bits & ISA_MASK); 
    #endif
    }
    

    我们重点看一下shiftcls,在shiftcls中存储着类对象和元类对象的内存地址信息,我们上文讲到,对象的isa指针需要同ISA_MASK经过一次按位与运算才能得出真正的类对象地址.那么我们将ISA_MASK的值0x0000000ffffffff8ULL转化为二进制数分析一下:

    66F83639-6D89-4BF0-A240-801D812183E8.png
    从图中可以看到ISA_MASK的值转化为二进制中有33位都为1,上文讲到按位与运算是可以取出这33位中的值.那么就说明同ISA_MASK进行按位与运算就可以取出类对象和元类对象的内存地址信息. 我们继续分析一下结构体位域中其他的内容代表的含义:
    949619A6-DC1C-45DF-8D74-C84C520156DB.png

    isa是OC对象的第一个属性,因为这一属性是来自于继承,要早于对象的成员变量,属性列表,方法列表以及所遵循的协议列表。**经过calloc申请内存的时候,这个指针是怎么和TCJPerson这个类所关联的呢?通过分析对象的alloc方法可以定位到:obj->initInstanceIsa(cls, hasCxxDtor)initIsa(cls, true, hasCxxDtor)

    inline void 
    objc_object::initIsa(Class cls, bool nonpointer, bool hasCxxDtor) 
    { 
        assert(!isTaggedPointer()); 
        
        if (!nonpointer) {
            isa.cls = 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;
        }
    }
    

    以上{}代码简化后(仅供参考):

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

    3:isa指向走位分析

    `LLDB`相关的指令解析:
    1、`x obj`:打印对象内存信息(小端模式);`x/4gx obj`:16进制打印对象前4个字节内存信息
    2、`p`:打印对象地址;   `p/x`:16进制打印 ;`p/d`:10进制打印;`p/o`:8进制打印; `p/t`:2进制打印
    3、`bt`打印当前堆栈信息
    

    对象的内存信息,3段内存地址分布指向:isa(8字节),superclass(8字节),cache(16字节)

    (lldb) x/4gx dog
    0x1021024b0: 0x001d8001000011e9 0x0000000000000000
    0x1021024c0: 0x001dffff888b72b9 0x000000010000078c
    

    对象的isa指针最终指向根元类


    25C8F8DE-AC72-4DFC-806F-370A664C5F44.png

    还原isa,获取shiftcls即类对象。实体对象的isa做两次位移>> 3``<< 17操作,原理请参考ISA_BITFIELD

    701FFF35-0C7B-4C2B-911C-622CAB697001.png

    4、附件

    isa和superClass走位图

    isa流程图.png
    69B0409D-2B7B-4988-ABF3-4673A69F1A38.jpeg

    相关文章

      网友评论

          本文标题:ios的对象、类和isa分析以及LLDB调试

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