iOS底层原理探究05-类的底层原理isa链&继承链&类的内存结

2021-08-05  本文已影响0人  superFool

isa指向分析

通过《iOS底层原理探究04-OC对象的本质&联合体位域&isa分析》我们对isa已经有了一定的了解,现在我们来研究下isa具体的指向情况。

对象的isa指向

对象的isa指向类这个我们都知道下面就来验证一下
这里需要知道nopointerisa的存在,普通的isa是直接指向类的,但是得添加OBJC_DISABLE_NONPOINTER_ISA = YES参数能会使用普通的isa否则默认会使用nopointerisa

image.png
当我们不使用nopointerisaisa是直接指向类的
image.png

类的isa指向

现在我们验证了对象的isa是指向类的,那么类的isa又指向什么呢,我们来继续探究

image.png

通过上面的一通操作我们得到了这样一个结果0x00000001000083600x0000000100008338 都输出 LGPerson,这里我们大胆猜想一下 ,是不是类和对象一样在内存里也会存在很多个,接下来我们验证一下这个猜想

image.png
我们使用各种方法得到的类打印出来的地址都是同一个,说明我们的猜想不正确。
用烂苹果(MachOView)看一下吧
image.png
把Products文件夹下编译生成的可执行文件(002-isa分析)放到烂苹果里分析下
image.png
到符号表(Symbol Table)里查看

此时我会类的isa指向元类,那元类的isa指向哪里呢?接下来继续探究一下

image.png
输出元类的isa & ISA_MASK发现指向的是根元类NSObject,进一步输出跟元类的isa & ISA_MASK是指向自己的,这个我们输出NSObject类的地址发现和根元类NSObject的地址并不相同。至此我们可以得到这样一个结论
isa走位图
一般的类isa的走位图是这样的,那么根类NSObject的isa的走位图又是什么样的呢
image.png
可以看到NSObject还是有点区别的比普通的类少了一层
NSObject的isa走位图
结合到一起就得到了完整的isa走位图
完整的isa走位图
接下来看下继承链
继承链
运行结果说明

类的内存结构

《iOS底层原理探究04-OC对象的本质&联合体位域&isa分析》里面我们已经知道了OC的类Class在底层是struct objc_class *类型,接下来就来看看objc_class的结构

struct objc_class : objc_object {
    ...省略无关代码
    // Class ISA;
    Class superclass;
    cache_t cache;             // formerly cache pointer and vtable
    class_data_bits_t bits;    
    ...省略无关代码
}

因为类objc_class有好几百行代码,我们省略掉无关紧要的代码和方法代码(结构体的方法不在接口对象里存而是在方法区保存),直接研究他的成员变量,可以得到objc_class包含4个成员变量

struct cache_t {
private:
    explicit_atomic<uintptr_t> _bucketsAndMaybeMask;
    union {
        struct {
            explicit_atomic<mask_t>    _maybeMask;
#if __LP64__
            uint16_t                   _flags;
#endif
            uint16_t                   _occupied;
        };
        explicit_atomic<preopt_cache_t *> _originalPreoptCache;
    };
...省略静态变量
...省略无关方法代码
}

因为静态变量是存在静态存储区的,方法存在方法区都不影响cache_t结构体的大小所以省略掉这些代码,下面就很清晰了就剩一个explicit_atomic<uintptr_t> _bucketsAndMaybeMask和联合体。

那么拿到class的首地址 + 32就是bits的位置。

image.png
按我们之前的思路确实能获取到class_data_bits_t *类型的bits,但是怎么看这个数据结构呢。
struct class_data_bits_t {
...省略无关代码
public:

    class_rw_t* data() const {
        return (class_rw_t *)(bits & FAST_DATA_MASK);
    }
...省略无关代码
}

通过源码可以找到class_data_bits_t中存在data ()取数据的方法我们来试一下。

image.png
通过data()方法确实取到了class_rw_t,打印得到了class_rw_t的数据结构,再来看下class_rw_t的源码。
struct class_rw_t {
    ...省略无关代码
    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};
        }
    }
};
(lldb) x/4gx LGPerson.class
0x100008380: 0x00000001000083a8 0x000000010036c140
0x100008390: 0x0000000100364360 0x0000802800000000
(lldb) p/x 0x100008380+0x20
(long) $1 = 0x00000001000083a0
(lldb) p (class_data_bits_t *)0x00000001000083a0
(class_data_bits_t *) $2 = 0x00000001000083a0
(lldb) p $2->data()
(class_rw_t *) $3 = 0x0000000101349aa0
(lldb) p *$3
(class_rw_t) $4 = {
  flags = 2148007936
  witness = 0
  ro_or_rw_ext = {
    std::__1::atomic<unsigned long> = {
      Value = 4295000344
    }
  }
  firstSubclass = nil
  nextSiblingClass = NSUUID
}
(lldb) p $4.methods()
(const method_array_t) $5 = {
  list_array_tt<method_t, method_list_t, method_list_t_authed_ptr> = {
     = {
      list = {
        ptr = 0x0000000100008160
      }
      arrayAndFlag = 4295000416
    }
  }
}
(lldb) p $5.list
(const method_list_t_authed_ptr<method_list_t>) $6 = {
  ptr = 0x0000000100008160
}
(lldb) p $6.ptr
(method_list_t *const) $7 = 0x0000000100008160
(lldb) p *$7
(method_list_t) $8 = {
  entsize_list_tt<method_t, method_list_t, 4294901763, method_t::pointer_modifier> = (entsizeAndFlags = 27, count = 6)
}
(lldb) p $8.get(0).big()
(method_t::big) $9 = {
  name = "sayNB"
  types = 0x0000000100003f77 "v16@0:8"
  imp = 0x0000000100003d40 (KCObjcBuild`-[LGPerson sayNB])
}
(lldb) p $8.get(1).big()
(method_t::big) $10 = {
  name = "hobby"
  types = 0x0000000100003f6f "@16@0:8"
  imp = 0x0000000100003db0 (KCObjcBuild`-[LGPerson hobby])
}
(lldb) p $8.get(2).big()
(method_t::big) $11 = {
  name = "setHobby:"
  types = 0x0000000100003f8b "v24@0:8@16"
  imp = 0x0000000100003de0 (KCObjcBuild`-[LGPerson setHobby:])
}
(lldb) p $8.get(3).big()
(method_t::big) $12 = {
  name = "init"
  types = 0x0000000100003f6f "@16@0:8"
  imp = 0x0000000100003ce0 (KCObjcBuild`-[LGPerson init])
}
(lldb) p $8.get(4).big()
(method_t::big) $13 = {
  name = "name"
  types = 0x0000000100003f6f "@16@0:8"
  imp = 0x0000000100003d50 (KCObjcBuild`-[LGPerson name])
}
(lldb) p $8.get(5).big()
(method_t::big) $14 = {
  name = "setName:"
  types = 0x0000000100003f8b "v24@0:8@16"
  imp = 0x0000000100003d80 (KCObjcBuild`-[LGPerson setName:])
}
(lldb) 

为什么需要调用big(),来看源码

image.png
method_t的结构里方法的信息存储在struct big结构体里,所以需要调用big()方法

总结

通过对类的结构的探究,了解到类的方法列表属性列表协议列表都储存在bits数据结构中,后面我们会继续对类的存储探究。

遗留问题

本篇留下了一个问题:类的实例方法是存在methods()数据接口中的但是类方法并没有存在这里,那类方法存在哪里呢?我再下一篇《iOS底层原理探究06-类的底层原理下》中为您解答

上一篇 下一篇

猜你喜欢

热点阅读