iOS底层代码探索003-类的底层探索

1.环境准备

版本为12.5的Xcode的编译器

Objc debug源代码

配置可以参考这篇博客https://juejin.cn/post/6844903959161733133

2.问题抛出

2.1 现象

经过类对象的底层探索,我们知道

  • 一个类对象有个isa指针,与上isa_mask就能得出类指针的地址。
  • NSObject的底层实现是objc_object结构体。
  • class的底层实现是objc_class 结构体指针。

实际上,根据源码,我们还知道objc_class继承了objc_object,故类里面也有isa指针。我们可以通过打印类指针得到类里面的isa指针。

我们将类里的isa指针与上isa_mask,又发现了一个TWPerson类。

2.2 分析与问题

为什么呢?难道内存里有好多个不同地址的TWPerson类?

类对象里的isa和类里的isa指向的不同地址的TWPerson类是同一个类么?

猜想:内存中会创建不止一个TWPerson类?

今天就让我们好好探究一下类。

3.METACLASS元类

3.1 元类的发现

那我们多创建几个TWPerson类对象,打印出他们的类的地址。

//MARK: - 分析类对象内存存在个数
void TestClassNum(void){
    Class class1 = [TWPerson class];
    Class class2 = [TWPerson alloc].class;
    Class class3 = object_getClass([TWPerson alloc]);
    Class class4 = [TWPerson alloc].class;
    NSLog(@"\n%p\n%p\n%p\n%p",class1,class2,class3,class4);
}

故之前的应该不是TWPerson类。

我们可以通过MachOView工具查看程序运行的内存,搜索关键词TWPerson。

除了_OBJC_CLASS_$_TWPerson还有一个_OBJC_METACLASS_$_TWPerson。

我们称之为元类。

3.2 isa的走位图

我们打印元类内存,并继续用元类的isa指针与上isa_mask,看看会发生什么:

发现元类的isa指针能得到NSObject,这个NSObject是元类NSObject还是NSObject类呢?

 // NSObject实例对象
    NSObject *object1 = [NSObject alloc];
    // NSObject类
    Class class = object_getClass(object1);
    // NSObject元类
    Class metaClass = object_getClass(class);
    // NSObject根元类
    Class rootMetaClass = object_getClass(metaClass);
    // NSObject根根元类
    Class rootRootMetaClass = object_getClass(rootMetaClass);
    NSLog(@"\n%@ %p NSObject实例对象\n%@ %p NSObject类\n%@ %p META NSObjcet元类\n%@ %p ROOT META NSObject根元类\n%@ %p ROOT ROOT META NSObject根根元类",object1,object1,class,class,metaClass,metaClass,rootMetaClass,rootMetaClass,rootRootMetaClass,rootRootMetaClass);

这下就清楚了,TWPerson的元类的isa的类指针是指向NSObject的元类,而不是NSObject类。

3.2 元类的继承链

// TWPerson元类
    Class pMetaClass = object_getClass(TWPerson.class);
    Class psuperClass = class_getSuperclass(pMetaClass);
    NSLog(@"\nMETA TWPerson:%@ %p\nSUPER META TWPerson父类:%@ %p",pMetaClass,pMetaClass,psuperClass,psuperClass);

TWPerson的元类的父类是NSObject的元类。

我们新建一个TWTeacher类,作为TWPerson的子类。

// TWTeacher -> TWPerson -> NSObject
    // 元类也有一条继承链
    Class tMetaClass = object_getClass(TWTeacher.class);
    Class tsuperClass = class_getSuperclass(tMetaClass);
    NSLog(@"META TWTeacher %@ %p",tMetaClass,tMetaClass);
    NSLog(@"SUPER META TWTeacher %@ %p",tsuperClass,tsuperClass);

 // NSObject 类的superclass 
    Class nsuperClass = class_getSuperclass(NSObject.class);
    NSLog(@"SUPER NSObject: %@ %p",nsuperClass,nsuperClass);

 // 元类的superclass -> NSObject
    Class rnsuperClass = class_getSuperclass(metaClass);
    NSLog(@"SUPER META NSObject: %@ %p",rnsuperClass,rnsuperClass);

到这里,我们可以画出一张类的继承图。 

3.3 总结 isa链&继承链 苹果官方图:

4.类的信息存储探索

4.1 内存偏移

在探索类里面的信息存储之前,我们先通过一张图简单了解一下什么是内存偏移:

 

4.2 objc_class结构体

 

第一个是isa。 第二个是父类,验证一下:

 

4.3 bits探索

4.3.1 bits的偏移量计算

我们先探究bits,要探究bits 需要知道isa superclass cache的大小,知道得到bits的偏移量。Isa和super class是8+8=16字节。

  • staic存在数据段中,不在结构体内存中。
  • 方法存在代码段中,不在结构体内存中。

故只要看cache_t里的这两个属性。

8字节

4字节

_flags 2字节 _occupied 2字节

故结构体就是8字节

下面的是个指针,故也是8字节。

由于是共用体,占据最大属性的内存大小,故共用体的内存大小就是8字节。

故cache_t的总大小是8+8=16字节。

故偏移量=16+16=32=0x20

这样我们就知道bits在内存中的位置了。

4.3.2 类中的数据获取

class_rw_t *data() const {
        return bits.data();
    }

 

struct class_rw_t {
    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;

private:
  //...
public:
  //... 
    const class_ro_t *ro() const {
        auto v = get_ro_or_rwe();
        if (slowpath(v.is<class_rw_ext_t *>())) {
            return v.get<class_rw_ext_t *>(&ro_or_rw_ext)->ro;
        }
        return v.get<const class_ro_t *>(&ro_or_rw_ext);
    }

    void set_ro(const class_ro_t *ro) {
    //...
    }

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

可以通过class_rw_t指针获取到类里的信息。

4.3.3 类的属性

class property_array_t : 
    public list_array_tt<property_t, property_list_t, RawPtr>
{
    typedef list_array_tt<property_t, property_list_t, RawPtr> Super;

 public:
    property_array_t() : Super() { }
    property_array_t(property_list_t *l) : Super(l) { }
};

 

给TWPerson类加个属性 name/成员变量 obj/对象方法 sayhi/类方法 saybye,我们再去找找。

我们依然可以在properties中找到name,但是找不到obj。

 

4.3.4 类的对象方法

在class_rw_t指针,我们还有个methods。里面应该存了方法,发现读取不到。

 

 

 

通过阅读源码,可以发现,原来方法信息都存在了结构体big中。难怪我们之前没有查看到。

通过big,我们就能看到方法了。但是发现其中并没有类方法saybye。

(lldb) p $15.get(0).big()

(method_t::big) $26 = {

  name = "sayhi"

  types = 0x0000000100003f63 "v16@0:8"

  imp = 0x0000000100003b40 (TWbit`-[TWPerson sayhi] at TWPerson.m:12)

}

(lldb) p $15.get(1).big()

(method_t::big) $27 = {

  name = "array1"

  types = 0x0000000100003f6b "@16@0:8"

  imp = 0x0000000100003ba0 (TWbit`-[TWPerson array1] at TWPerson.h:17)

}

(lldb) p $15.get(2).big()

(method_t::big) $28 = {

  name = "setArray1:"

  types = 0x0000000100003f73 "v24@0:8@16"

  imp = 0x0000000100003bc0 (TWbit`-[TWPerson setArray1:] at TWPerson.h:17)

}

(lldb) p $15.get(3).big()

(method_t::big) $29 = {

  name = "setMArray:"

  types = 0x0000000100003f73 "v24@0:8@16"

  imp = 0x0000000100003c80 (TWbit`-[TWPerson setMArray:] at TWPerson.h:19)

}

(lldb) p $15.get(4).big()

(method_t::big) $30 = {

  name = "name"

  types = 0x0000000100003f6b "@16@0:8"

  imp = 0x0000000100003cc0 (TWbit`-[TWPerson name] at TWPerson.h:20)

}

(lldb) p $15.get(5).big()

(method_t::big) $31 = {

  name = "setArray:"

  types = 0x0000000100003f73 "v24@0:8@16"

  imp = 0x0000000100003c20 (TWbit`-[TWPerson setArray:] at TWPerson.h:18)

}

(lldb) p $15.get(6).big()

(method_t::big) $32 = {

  name = ".cxx_destruct"

  types = 0x0000000100003f63 "v16@0:8"

  imp = 0x0000000100003d30 (TWbit`-[TWPerson .cxx_destruct] at TWPerson.m:11)

}

(lldb) p $15.get(7).big()

(method_t::big) $33 = {

  name = "array"

  types = 0x0000000100003f6b "@16@0:8"

  imp = 0x0000000100003c00 (TWbit`-[TWPerson array] at TWPerson.h:18)

}

(lldb) p $15.get(8).big()

(method_t::big) $34 = {

  name = "setName:"

  types = 0x0000000100003f73 "v24@0:8@16"

  imp = 0x0000000100003cf0 (TWbit`-[TWPerson setName:] at TWPerson.h:20)

}

(lldb) p $15.get(9).big()

(method_t::big) $35 = {

  name = "mArray"

  types = 0x0000000100003f6b "@16@0:8"

  imp = 0x0000000100003c60 (TWbit`-[TWPerson mArray] at TWPerson.h:19) 

}

4.3.5 类的成员变量

那我们试试class_rw_t指针中的ro方法,会返回一个class_ro_t的指针。打印它的内容。 

 

好家伙,之前的method和properties都在里面。猜测ivars 里应该存的是成员变量吧,验证一下。

(lldb) p $39.ivars

(const ivar_list_t *const) $40 = 0x0000000100008228

(lldb) p *$40

(const ivar_list_t) $41 = {

  entsize_list_tt<ivar_t, ivar_list_t, 0, PointerModifierNop> = (entsizeAndFlags = 32, count = 5)

}

(lldb) p $41.get(0)

(ivar_t) $42 = {

  offset = 0x0000000100008370

  name = 0x0000000100003eb2 "obj"

  type = 0x0000000100003f7e "@\"NSObject\""

  alignment_raw = 3

  size = 8

}

(lldb) p $41.get(1)

(ivar_t) $43 = {

  offset = 0x0000000100008378

  name = 0x0000000100003eb6 "_array1"

  type = 0x0000000100003f8a "@\"NSArray\""

  alignment_raw = 3

  size = 8

}

(lldb) p $41.get(2)

(ivar_t) $44 = {

  offset = 0x0000000100008380

  name = 0x0000000100003ebe "_array"

  type = 0x0000000100003f8a "@\"NSArray\""

  alignment_raw = 3

  size = 8

}

(lldb) p $41.get(3)

(ivar_t) $45 = {

  offset = 0x0000000100008388

  name = 0x0000000100003ec5 "_mArray"

  type = 0x0000000100003f95 "@\"NSMutableArray\""

  alignment_raw = 3

  size = 8

}

(lldb) p $41.get(4)

(ivar_t) $46 = {

  offset = 0x0000000100008390

  name = 0x0000000100003ecd "_name"

  type = 0x0000000100003fa7 "@\"NSString\""

  alignment_raw = 3

  size = 8

}

果然,成员变量都存在了ivar中,在这里,我们找到了之前没找到的成员变量obj。

4.3.6 类的类方法(存在了元类中)

现在就差一个类方法不知道存在哪里了。而我们在之前打印出的ro指针中,其实发现TWPerson.class中的内容已经探索的差不多了。你在里面死命找也不会找到TWPerson的类方法的。(因为我已经找过了)

突然,我们想起了之前发现的一个还不知道作用的元类,我们可以有个猜想,类方法可能存在了TWPerson的元类中。

先找到元类的地址:

 打印出元类的内容,和之前探索TWPerson类一样。

 

 

终于找到了类方法 saybye!

5.结尾

也到了我们say bye的时候,到这里,我们把TWPerson类的成员变量、属性、对象方法、类方法都在内存中找到了!

如有问题,欢迎大家留言,与我交流。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值