类的探究分析

2021-06-23  本文已影响0人  冼同学

准备工作

内存偏移

普通指针代码分析:

int main(int argc, const char * argv[]) {
    @autoreleasepool {
       int a = 10; 
        int b = 10; 
        int *a_p = &a;
        int *b_p = &b;
        NSLog(@"%d -- %p -- %p",a,&a,&a_p);
        NSLog(@"%d -- %p -- %p",b,&b,&b_p);
}

//打印结果
内存偏移[86667:1854956] 10 -- 0x7ffeefbff47c -- 0x7ffeefbff470
内存偏移[86667:1854956] 10 -- 0x7ffeefbff478 -- 0x7ffeefbff468
int main(int argc, const char * argv[]) {
    @autoreleasepool {
       XXPerson *p1 = [LGPerson alloc];
        XXPerson *p2 = [LGPerson alloc];
        NSLog(@"%@ -- %p",p1,&p1);
        NSLog(@"%@ -- %p",p2,&p2);

//打印结果
内存偏移[86667:1854957] <LGPerson: 0x100796fb0> -- 0x7ffeefbff460
内存偏移[86667:1854957] <LGPerson: 0x10078b730> -- 0x7ffeefbff458
}
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        int c[4] = {1,2,3,4};
        int *d   = c;
        NSLog(@"%p - %p - %p",&c,&c[0],&c[1]);
        NSLog(@"%p - %p - %p",d,d+1,d+2);

        for (int i = 0; i<4; i++) {
            int value =  *(d+i);
            NSLog(@"%d",value);
        }
}
//打印结果
内存偏移[86667:1854956] 0x7ffeefbff490 - 0x7ffeefbff490 - 0x7ffeefbff494
内存偏移[86667:1854956] 0x7ffeefbff490 - 0x7ffeefbff494 - 0x7ffeefbff498
//循环打印结果
内存偏移[86667:1854956] 1
内存偏移[86667:1854956] 2
内存偏移[86667:1854956] 3
内存偏移[86667:1854956] 4

类isa走位的分析

通过之前的文章可以知道对象本质是结构体,结构体的第一个成员变量就是isa。那么类的结构是什么有什么?类有isa指向嘛?如果有他们之间的关系是怎么样的?那么针对这些问题我们进行以下的分析。

isa的走位图(官方的)

isa走位图

iOS不同架构下的isa掩码

类对象的内存个数

int main(int argc, char * argv[]) {
    @autoreleasepool {
        Class class1 = [MyPersion class];
        Class class2 = [MyPersion alloc].class;
        Class class3 = object_getClass([MyPersion alloc]);
        Class class4 = [MyPersion alloc].class;
        NSLog(@"\n-%p-\n-%p-\n-%p-\n-%p-",class1,class2,class3,class4);  
    }
    return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
打印结果:
2021-06-18 15:57:27.779872+0800 test[1355:490263] 
-0x102e21818-
-0x102e21818-
-0x102e21818-
-0x102e21818-

得出结论:类的内存地址分配都是一样的,每个类只有一个内存块,这根对象的内存分配不一样。

对象的类isa指向(元类的引出)

我自己创建了一个persion类继承于NSObject类,代码分析如下:

详细分析
分析:

根元类的引出

参照以上的想法,元类会不会也有isa,isa指向是什么?很简单,那就实践实践一下呗!请以下操作:

根元类引出
分析:

总结:

经过上面代码的层层分析,我们验证么isa的走位图的isa走位流程:objc(对象) --> class(类) --> metaClass(元类) --> rootMetaClass(根元类) --> rootMetaClass(根元类自己)。
isa走位图:

isa走位图

类的继承链分析

用oc代码看看继承链的原理,创建XXTeacher类继承于XXPerson类,如下图:

继承链代码
分析:NSObject的父类打印的结果是nilXXTeacher的元类的父类是XXPerson的元类(XXPerson的元类的地址和XXPerson类的地址不一样)。XXPerson的元类的父类是NSObject的元类,NSObject的元类的父类是NSObject(和NSObject类的地址一样)

官方isa走位图和继承图的还原:

还原图

类的结构分析

查看objc4(818版本)的源码,找到了objc_class结构如下:

struct objc_class : objc_object {
  objc_class(const objc_class&) = delete;
  objc_class(objc_class&&) = delete;
  void operator=(const objc_class&) = delete;
  void operator=(objc_class&&) = delete;
    // 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 getSuperclass() const {
#if __has_feature(ptrauth_calls)
#   if ISA_SIGNING_AUTH_MODE == ISA_SIGNING_AUTH
        if (superclass == Nil)
            return Nil;

 .........    //源码位置为objc-runtime-new.h文件第1688行-2173行
//省略了好多代码

分析

探究cache_t内存大小

我们在开发过程中看类主要看类的属性和方法,上面所述类的属性和方法都存放在class_data_bits_t结构体中,那么我们必须要知道class_data_bits_t结构体的地址,所以分析cache_t结构体内存大小是非常必要的。上代码:

struct cache_t {
private:
    explicit_atomic<uintptr_t> _bucketsAndMaybeMask;           //占用8个字节
    union {  //联合体大小只关心内存最大的成员
        struct {
            explicit_atomic<mask_t>    _maybeMask;   //占用4个字节
#if __LP64__
            uint16_t                   _flags;    //占用2个字节,现在的objc版本只能进入这个判断
#endif
            uint16_t                   _occupied;     //占用2个字节
        };
        explicit_atomic<preopt_cache_t *> _originalPreoptCache;  //占用8个字节
    };

#if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_OUTLINED
    // _bucketsAndMaybeMask is a buckets_t pointer
    // _maybeMask is the buckets mask

    static constexpr uintptr_t bucketsMask = ~0ul;
    static_assert(!CONFIG_USE_PREOPT_CACHES, "preoptimized caches not supported");
   .........忽略与结构体内存无关的代码
  //源码位置为objc-runtime-new.h文件第338行-550行

分析:

分析class_data_bits_t bits结构体

综上所述,class_data_bits_t bits结构体记录的是类的属性、成员变量以及方法。所以必须要了解结构体里面有什么哦!上代码:

struct class_data_bits_t {
    friend objc_class;

    // Values are the FAST_ flags above.
    uintptr_t bits;
private:
    bool getBit(uintptr_t bit) const
    {
        return bits & bit;
    }

    // Atomically set the bits in `set` and clear the bits in `clear`.
    // set and clear must not overlap.
    void setAndClearBits(uintptr_t set, uintptr_t clear)
    {
        ASSERT((set & clear) == 0);
        uintptr_t newBits, oldBits = LoadExclusive(&bits);
      //此处省略部分代码
public:

    class_rw_t* data() const {
        return (class_rw_t *)(bits & FAST_DATA_MASK);
    }
    void setData(class_rw_t *newData)
    {
   //此处省略部分代码

class_rw_t结构体

struct class_rw_t {
    // Be warned that Symbolication knows the layout of this structure.
    uint32_t flags;
    uint16_t witness;
#if SUPPORT_INDEXED_ISA
    uint16_t index;
#endif

    explicit_atomic<uintptr_t> ro_or_rw_ext;

    Class firstSubclass;
    Class nextSiblingClass;
//此处省略部分代码
const method_array_t methods() const {    //获取方法列表的方法
        auto v = get_ro_or_rwe();
        if (v.is<class_rw_ext_t *>()) {
            return v.get<class_rw_ext_t *>(&ro_or_rw_ext)->methods;
        } else {
            return method_array_t{v.get<const class_ro_t *>(&ro_or_rw_ext)->baseMethods()};
        }
    }

    const property_array_t properties() const {   //获取属性的方法
        auto v = get_ro_or_rwe();
        if (v.is<class_rw_ext_t *>()) {
            return v.get<class_rw_ext_t *>(&ro_or_rw_ext)->properties;
        } else {
            return property_array_t{v.get<const class_ro_t *>(&ro_or_rw_ext)->baseProperties};
        }
    }

    const protocol_array_t protocols() const {         //获取协议的方法
        auto v = get_ro_or_rwe();
        if (v.is<class_rw_ext_t *>()) {
            return v.get<class_rw_ext_t *>(&ro_or_rw_ext)->protocols;
        } else {
            return protocol_array_t{v.get<const class_ro_t *>(&ro_or_rw_ext)->baseProtocols};
        }
  //此处省略部分代码

分析:
是不是很迷茫?当我们看到那么多代码时候该怎么做?首先我们需要看这个结构提供了什么方法跟属性,这是非常重要的!!!

获取类的属性、方法操作流程分析

获取类的属性

1.创建好XXPersion类,如图所示:

XXPerson类
2.断点,进行lldb调试,如图所示:
获取属性1
获取属性2
步骤:

获取类的方法

获取类方法1
获取属性方法2
步骤:NSObject.class -> class_data_bits_t -> class_rw_t -> method_array_t -> method_list_t -> method_t-> big

成员变量与类方法的获取

成员变量获取

观察发现class_rw_t还有一个获取class_ro_t *的方法,会不会在class_ro_t中,源码查看class_ro_t的类型。

struct class_ro_t {
    uint32_t flags;
    uint32_t instanceStart;
    uint32_t instanceSize;
#ifdef __LP64__
    uint32_t reserved;
#endif

    union {
        const uint8_t * ivarLayout;
        Class nonMetaclass;
    };

    explicit_atomic<const char *> name;
    // With ptrauth, this is signed if it points to a small list, but
    // may be unsigned if it points to a big list.
    void *baseMethodList;
    protocol_list_t * baseProtocols;
    const ivar_list_t * ivars;              //存储成员变量

    const uint8_t * weakIvarLayout;
    property_list_t *baseProperties;
//省略了部分代码(objc-runtime-new.h文件第1037行-1171行)
}

分析:

类方法获取

对象的方法是存储在类中,那么类方法可能存储在元类中。那么实践一下咯。
lldb调试

类方法获取
步骤:

总结:

附上类探究的整个流程图:

总流程
学习过程的确艰辛!但是学到知识让我非常兴奋!通过对类结构的深入学习,使得我对底层的理解更加深刻了,自身的专业知识又有了进一步的提升。让我们继续加油吧💪🏻!
上一篇 下一篇

猜你喜欢

热点阅读