博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
iOS底层代码探索003-类的底层探索
阅读量:3959 次
发布时间:2019-05-24

本文共 8006 字,大约阅读时间需要 26 分钟。

1.环境准备

版本为12.5的Xcode的编译器

配置可以参考这篇博客

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类。

我们可以通过工具查看程序运行的内存,搜索关键词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
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
())) { return v.get
(&ro_or_rw_ext)->ro; } return v.get
(&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
()) { return v.get
(&ro_or_rw_ext)->methods; } else { return method_array_t{v.get
(&ro_or_rw_ext)->baseMethods()}; } } const property_array_t properties() const { auto v = get_ro_or_rwe(); if (v.is
()) { return v.get
(&ro_or_rw_ext)->properties; } else { return property_array_t{v.get
(&ro_or_rw_ext)->baseProperties}; } } const protocol_array_t protocols() const { auto v = get_ro_or_rwe(); if (v.is
()) { return v.get
(&ro_or_rw_ext)->protocols; } else { return protocol_array_t{v.get
(&ro_or_rw_ext)->baseProtocols}; } }};

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

4.3.3 类的属性

class property_array_t :     public list_array_tt
{ typedef list_array_tt
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类的成员变量、属性、对象方法、类方法都在内存中找到了!

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

转载地址:http://fbqzi.baihongyu.com/

你可能感兴趣的文章