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

2020-01-10  本文已影响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
上一篇 下一篇

猜你喜欢

热点阅读