iOS底层探索 --- 类的结构探索(上)

image

今天我们将进行类的结构体的探索,其中有些内容我们在iOS底层探索 ---Runtime(一)--- 基础知识有做过探索。不过没关系,今天我们再来回顾一下。

本章我们将探索一下内容:

1、类的结构(类,元类,根类之间的关系)

2、objc_object & objc_class

3、类中信息的探索


1、类的结构(类,元类,根类之间的关系)

1.1 类(Class)

我们都知道,我们创建的基本上都继承自NSObject,而NSObject里面有一个默认参数isa;也就是说,继承自NSObject里面都会有一个isa。如下:

image

假设我们现在有一个Person类,那么Person对象的首地址就是isa地址,同样的isa也代表当前的类:

image

1.2 元类(Meta Class)

这个时候,我们如果继续查看类地址,会打印什么呢?下面我们就来尝试一下:

image

可以看到,继续打印类地址,输出的是类的内存分布
我们还可以通过x Person.class来查看类的内存分布

image
  • 此时有一点大家注意了,我们在上面通过isa打印出来了Person。对应的类地址是0x00000001000080e8
  • 同时我们还打印出了类的内存分布信息。如果我们继续打印类的内存分布信息的首地址,会发生什么呢?打印结果如下:
image

大家可以看到,此时我们同样打印出了Person。这就奇怪了!!!

  • 第一点:首先我们通过isa打印出Person,这一点相信大家没有什么问题。(有疑问的同学可以参考这一篇文章iOS底层探索 --- OC对象原理(下)

  • 第二点:为什么我们通过0x1000080e8也能都打印出Person呢?明明是两个不同的地址呀!!!这里就要引出元类(Meta Class)这个知识点了

1、Meta Class(元类)就是类对象所属的。一个对象所属的类叫做类对象,而一个类对象所属的类就叫做元类

2、Meta Class(元类)的定义和创建,都是由编译器自动完成。

3、所有的类方法,都存储在Meta Class(元类)中。


1.3 根类

在上面我们已经找到了Meta Class (元类),那我们继续往下去寻找又会有什么发现呢?

image

我们会发现,居然指向了NSObject

此时我们我们如果执行p/x NSObject.class这句指令,打印出来的NSObject地址会是什么呢?

image

此时有没有发现问题,NSObject的地址不一样呀。这是怎么回事呢?

这是因为p/x NSObject.class中打印的NSObject并不是根类,而是根元类。怎么证明呢?我们只需要打印一下NSObject.class的内存信息,就会看到起isa上面打印的NSObject的地址是一样的。

换句话说,就是元类isa 指向了根元类

(注:严谨一点是要& ISA_MASK,这里由于isa中只有shifcls信息,可以直接看出来)。执行结果如下:

image
  • 此时我们已经跟踪到了根类元类,如果我们在根类元类的基础上,继续追踪,又会出现什么情况呢?那我们就继续试一下:
    image

    大家有没有发现,根类元类isa指向了自己。

此时就回到我们在iOS底层探索 ---Runtime(一)--- 基础知识中探索过的那幅图:

[图片上传失败...(image-caaf2f-1624499873237)]


1.4 类对象在内存中的个数

类对象在内存中,有且仅有一份!!!
这也是一道经常被问到的面试题,下面我们来证明一下:
我们创建多个类对象,然后打印,看一下地址是否相同

image

通过打印可以发现,虽然创建了多个类对象,但是地址都是一样的。因此我们可以说:类对象在内存中,有且仅有一份。


1.5 类的继承关系的坑点

  • 假设有一个类Man,继承自类Person;同时Man有一个对象mPerson有一个对象p
    那么问:mp有什么关系?
    答案:没有关系。(注意:一定不要说是继承关系)

  • 我们都知道子类继承自父类父类继承自根类
    那么问:根类继承自什么?
    答案:根类的继承关系来自于nil。(注意:根类不是继承自自己)


2 objc_object & objc_class

大家还记不记得,我们在iOS底层探索 --- OC对象原理(下)中,在我们转换mian.cpp文件中有这样一段代码:

image

当时我们只是对objc_classobjc_object简单的讲解了一下。那么它们两个具体是什么样子的,又有什么样的关系呢?今天我们来详细的探索一下。

2.1 源码寻找

首先我们要在源码中去寻找objc_objectobjc_class到底长什么样子。

  • objc_class
    我们在源码中搜索objc_class的时候,会出现两个不同的定义,其中有一个是已经废弃的。

  • 废弃的objc_class:

    image

  • 新的objc_class(可以看到新的objc_class继承自objc_object):

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() const {
       return bits.data();
   }
   void setData(class_rw_t *newData) {
       bits.setData(newData);
   }

   ......
   ......
   ......
}
  • objc_object
    同样的,在源码中搜索objc_object的时候,也搜索到两个
  • 位于objc.h文件里面的:
image
  • 位于objc-privat.h文件里面的:
image

通过main.cpp文件,我们可以确定,使用的是objc.h里面的objc_object(看注释,这个是给类的实例对象用的)。


2.2 objc_classobjc_object之间的关系

  1. 结构体objc_class继承自结构体objc_object

  2. objc_object有一个isa属性,所以objc_class也有一个isa。(在新的objc_class中有一段注释// Class ISA;,也从侧面表现了这一点。)

  3. NSObject也拥有isa。因为NSObject在底层的表现形式就是objc_object。或者说,所有的NSObject对象,都拥有isa

大家在捋这层关系的时候,要注意objc_object在源码中有两个定义,使用的范围也不一样。不要搞混了,不然会陷入死循环。

注意:objc_class继承的是objc-privat.h里面的objc_object


3 类中信息的探索

在日常开发过程中,我们定义一个类的时候,不仅仅是只有isa,还会有成员变量方法等等一些信息。下面我们我们就来看一下这些信息在哪里。

3.1 内存平移,获取bits信息

在探索类里面信息之前,我们要先达成一个共识,内存的查找是内存平移来获得的。也就是说objc_class里面的变量,是通过内存平移来获得的。举个例子如下:

image
image

objc_class中,isa8字节superclass也为8字节;我们不知道的是cache的内存大小。那么关键的就是获取cache的内存大小。

下面我们就来探索一下cache的大小,首先进入cache_t:

image

发现代码有很长,这个时候要怎么确定cache的大小呢?

注意:大家不要看到cache_t是一个结构体,就觉得cache8字节

结构体指针是8字节

结构体的大小要根据其内部的属性来计算。

计算的时候只计算属性,方法不算,静态的属性也不计算

通过上面我们就可以确定,cache内存大小的计算,只需要计算其中四个属性的大小(因为有if判断,大家可以仔细阅读以下这段源码。):

image

image
  • _buckets的类型为struct bucket_t *,是一个结构体指针;所以为8字节

  • _mask的类型为mask_tmask_tunsigned int的别名;所以为4字节

    image

    image

  • _maskAndBuckets的类型为uintptr_tmask_tunsigned long的别名;所以为8字节

    image

  • uint16_tunsigned short的别名;所以为2字节

整体算下来之后,cache的内存大小为8 + 4 + 2 + 2 = 16

那么bits的内存地址,就是首地址平移32个字节8 + 8 + 16

接下来我们就可以通过控制台打印一下bits信息了:

image

到这里,我们已经拿到了bits里面的信息了;但是我们并没有看到我们想要的成员变量方法等等的信息。

不要灰心,下面我们继续探索。


3.2 继续探索类信息

3.2.1 method(方法)

上面我们知道bits的类型是class_data_bits_t,那么我们就跟进去找一下data

image

继续跟进class_rw_t(这个里有个小技巧,我们可以先找method相关的内容,其他看不懂的内容可以忽略),我们发现了这个:

image

这就是我们要找的内容呀,方法是存放在方法列表里面的,而这里正好是一个array

  • method_array_t
    image

这个时候,我们继续在控制台操作:

image

我们给Person添加几个方法,再来查看一下:

image

)

重复上面的步骤,此时可以看到methods里面有信息了:

image

接下来就是去找我们的Method的了,这个控制台流程如下:

image

这里面有一个big()方法,是因为在objc4-818.2的版本中,method_list_t里面的元素是method_t,而method_t被重写了。

新的method_t长这个样子,我们要拿到方法名,就要通过big

image

method_t里面正好有对应的big()方法:
image

3.2.2 property(属性)
image

这个跟探索method前期是一样的操作,只不过后面获取property的时候略有不同;因为property_t直接就有name相关的属性:

image

整个控制台的操作如下:


image

3.2.3 会遇到的一些问题
  • 我们上面命名定义了属性jaxNum之外,还定义了一个成员变量NSString *jaxName,但是只能打印出一个jaxNum(打印第二个的时候,提示数组越界),因此我们可以说property_list里面没有成员变量
image

既然属性列表里面没有成员变量的信息,那我们就需要继续寻找了。

同样的bits->data()的返回类型是class_rw_t(注:上面已知);那我们就在class_rw_t里面找。(正常来讲,优先查找methods()附近的方法)

查找过之后,我们会发现,成员变量ivar在这里:

image

image

下面就看我们能不将成员变量打印出来了,打印流程如下:

image

通过上图我们发现,最后怎么没办法执行get方法了呢?
没关系,$7没法用get,那我们就使用$6:

image

最后成功打印成员变量jaxName

  • 在方法列表里面,找不到类方法
    我们都知道,类方法存储在元类中,因此在当前类method_lit_t中是找不到对应的类方法的。

这个时候,我们既要去元类中寻找;同样的根据我们查找当前类method_lidt_t的经验,我们在元类中也这样查找。查找流程如下:

image
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 207,248评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,681评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,443评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,475评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,458评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,185评论 1 284
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,451评论 3 401
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,112评论 0 261
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,609评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,083评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,163评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,803评论 4 323
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,357评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,357评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,590评论 1 261
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,636评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,925评论 2 344

推荐阅读更多精彩内容