类的原理(上)

对于一个iOS开发者来说对象是最熟悉不过的,因为我们开发的时候时刻都是在操作各种对象,而且都知道对象是通过了类的初始化创建出来的,那么问题来了,我们都知道类是通过继承实现方法以及属性和协议的传递,那么底层原理是怎么实现的呢?那么带着对未知的好奇心和求知欲我们一起探索类的原理,今天主要探索isa

知识补充:isa

我们都知道isa是个指针,那么他是一个什么样的指针呢?我们先创建一个NSObject对象,然后按住command键+鼠标右键点击,我们就可以进入类定义的地方,我们能够看到如下代码:

@interface NSObject <NSObject> {
    Class isa  OBJC_ISA_AVAILABILITY;
}

NSObject中有一个成员变量isa,而且是Class类型,然后我们继续看这个Class类型的时候会发现进不去了,我们看不到了。

161623895163_.pic.jpg

引用某人的口头禅好烦,那么接下来就上大招了,objc源码工程,我们从源码工程中进行搜索Class

171623895803_.pic.jpg

貌似有点多啊,但是经过我的不断查找(加上猜测),所以最终锁定这么一行代码:

typedef struct objc_class *Class;

瞬间明白为啥经常要说isa指针了,这不就是结构体指针嘛。然后再次引出来一个问题,我们都知道类继承就是isa幕后操手,那么继续看下这个objc_class结构体的构成,欲知后事如何,请听下回分析。

191623896379_.pic.jpg

知识补充:objc_class

对于结构体我们都不陌生,毕竟开发中也会经常遇到,那么这节我们一起看下objc_class结构体,继续在objc源码工程中进行全局搜索struct objc_class,(⊙o⊙)…貌似和我想的不一样,咋这么多:

image.png

机智如我,经过这么一观察发现,一个是objc-runtime-new.h、一个是objc-runtime-old.h,那么直接放弃objc-runtime-old.h因为我们现在用的是新版OBJC2,然后再去看下runtime.hobjc_class的定义如下:

struct objc_class {
    Class _Nonnull isa  OBJC_ISA_AVAILABILITY;

#if !__OBJC2__
    Class _Nullable super_class                              OBJC2_UNAVAILABLE;
    const char * _Nonnull name                               OBJC2_UNAVAILABLE;
    long version                                             OBJC2_UNAVAILABLE;
    long info                                                OBJC2_UNAVAILABLE;
    long instance_size                                       OBJC2_UNAVAILABLE;
    struct objc_ivar_list * _Nullable ivars                  OBJC2_UNAVAILABLE;
    struct objc_method_list * _Nullable * _Nullable methodLists                    OBJC2_UNAVAILABLE;
    struct objc_cache * _Nonnull cache                       OBJC2_UNAVAILABLE;
    struct objc_protocol_list * _Nullable protocols          OBJC2_UNAVAILABLE;
#endif

} OBJC2_UNAVAILABLE;

结构体最后有一个标记OBJC2_UNAVAILABLE意思就是告诉我们在OBJC2中不让用,那么我们就只需要去看objc-runtime-new.h中的声明了,愉快的打开objc-runtime-new.h,然后......

241623896963_.pic.jpg

这个结构体出乎意料了,好长,然后大概瞅了那么一圈,嘿,貌似不长,都是函数我们需要的是了解它的成员有那些,所以经过简化后,结果如下:

struct objc_class : objc_object {
    // Class ISA; 这块系统注释的原因是告诉我们,这个结构体还有一个影藏的成员```ISA```
    Class superclass; //父类
    cache_t cache;             // 用来缓存指针和虚函数表
    class_data_bits_t bits;    // 存储类的信息的,包括成员列表、属性列表、方法列表、协议列表等等
}

然后到这块基本上已经了解了isa指针的本质objc_class结构体,那么顺带我们瞄一眼objc_object,发现里面就一个成员isa_t isa;,至于isa_t的结构体的介绍,在我的对象的本质这篇博客中有详细介绍,在这里就不多做解释了,感兴趣可以去看下。

到这里前面的知识补充就完成了,也就是开胃小菜吃完了,那么开始今天的重点,类的继承链也即是我们经常能够看到的isa走位以及通过源码搭配lldb去分析查看类的结构。

类的superClass走位

1、首先我们看下类的继承链,我们打开Xcode->command+shift+N->macOS->Command Line Tool然后起个霸气的名字。

2、然后创建一个基于NSObject的类KGPerson,然后添加如下几个属性、成员变量、对象方法以及类方法:

image.png

3、然后基于KGPerson创建一个KGTeacher类。

4、然后在main.m中引入KGPersonKGTeacher的头文件。

5、声明并实现一个函数void kgTestSuperClass(void),然后在main函数中调用,具体代码如下:

image.png

然后我们运行程序,最后打印结果如下:

image.png

6、到此那么我们可以得出如下一个集成链的isa走位图:

未命名.png

类的isa走位

1、在基于上一过程中创建的项目以及代码基础上我们在main函数中添加一个函数,如下:

image.png

2、运行项目后得到的结果如下图所示:

image.png

3、分析输出的结果,我们可以得到如下的isa走位图:

未命名 (1).png

4、然后我们再看下superClassisa走位图,继续修改main.m代码如下:

image.png

5、运行结果如下:

image.png

6、通过打印结果,我们再次得到这样一个元类的superClass走位图:

未命名 (2).png

7、我们继续探索,元类的父类的isa是如何走的呢?下面继续修改main.m中的代码,如下:

image.png

8、代码运行结果如下:

image.png

9、最后结合之前的superClass走位图以及isa走位图,我们最后能够证明得到这个全球通用的图:

isa流程图.png

10、分析完成类的继承链以及元类的继承链后,回到开始的时候,我们的好奇心提出的疑问,下面我们一起通过源码搭配lldb去剖析类的原理。

类的属性

1、我们先前在KGPerson中创建了两个属性、一个成员变量、一个类方法、一个对象方法,下面我们先去看下类的属性是存储在哪里的。

2、我们前面以及探索了isa的结构,那么我们通过它的结构去查看存储的类信息,既然要看属性,那么我们必定是需要访问bits了它在类结构体中定义是这样的:class_data_bits_t bits;

3、我们需要访问的数据存储在bits里面,但是我们只能得到类的地址,也就是isa的地址,那么我们怎么去访问后面的元素呢?请看下面一个简单的示例:

image.png

然后输出如下:

image.png

4、从上面小测试我们就可以看到,我们可以通过指针地址的偏移去访问地址指向的存储空间中的值,那么我们接下来看下,我们想要访问bits中的内容,我们需要将指针移动多少位?我们前面看了objc_classbits前面有三个成员ISAsuperclasscache,然后isa是一个结构体指针占8位我们都知道,然后superclass是个Class类型,我们查看isa的时候就发现了Class也是个结构体指针,那就是说superclasss也是占8位,然后去看cache时发现这是个结构体,而且结构体中有两个成员,一个是_bucketsAndMaybeMask它的大小看uintptr_t的大小,uintptr_t又是一个无符号长整型数据,它在32位系统下是4字节,在64位系统下是8字节,我们现在都是在64位系统下进行的,所以它是8字节;另一个是联合体,然后联合体里面又是一个结构体和_originalPreoptCache,我们都知道联合体成员之间是互斥的,所以我们不看结构体,直接看_originalPreoptCache的大小,explicit_atomic<preopt_cache_t *> _originalPreoptCache;我们可以看到它的类型收到preopt_cache_t *的影响,然后preopt_cache_t *是个结构体指针,那么它的大小是8字节,所以得到如下结论:

image.png

5、所以我们如果想要访问bits需要进行地址偏移32位,地址是采用十六进制所以我们需要在原地址基础上加上0x20才可以访问到bits,那么我们先通过lldb测试下是否正确。

[图片上传失败...(image-c00025-1624257325729)]

6、从图上可以看出我们的猜测是完全正确的,确实偏移32位后能够访问到bits。然后我们继续分析class_data_bits_t结构体,发现里面有个data()方法,然后返回值是个class_rw_t*的指针,具体代码如下:

class_rw_t* data() const {
    return (class_rw_t *)(bits & FAST_DATA_MASK);
}

7、那么我们去看下这个class_rw_t结构体,发现好多方法和成员,但是滑动到最后的时候看到了几个熟悉的单词如下:

image.png

8、那么我们能否猜测,这几个方法就是读取属性、方法、协议方法的呢?带着好奇,我们先去看下properties,具体操作是在上次的基础上进行,如下:

202106191335_2_.gif

9、通过以上步骤我们验证了一个类的属性存储在bits中,我们可以通过以下流程来获取类的属性:

未命名.png

10、到这里属性在类中存储的位置已经找到了,那么接下来我们再去分析下成员变量存储的位置。

类的对象方法

1、上面两节我们探索了类存储属性以及成员变量的位置,那么这节开始探索类的对象方法的存储位置,前面介绍属性存储位置的时候,我们是通过properties()来获取的,同时我们找的时候也看到了有个methods()函数,那么我们可以做个大胆的猜测,是否这个函数就是获取方法列表的呢?下面我们通过lldb在源码工程中进行调试验证一下。

[图片上传失败...(image-4b7e-1624257459334)]

2、通过上述验证我们可以确定properties()中确实存储的方法,具体存储了哪些呢?下面我们继续读取:

image.png
954C82732570545214DD4E07059C38D4.gif

为啥不是我想象的那种样,出现我们熟悉的方法名呢?瞬间感觉好像在哪忽略了什么,于是我们再次回到class_rw_t结构体,然后顺着methods找到了method_array_t然后继续查找到了method_t结构体,看到如下内容:

image.png

发现我们需要的东西在big这个结构体内,然后我们继续往下查找,发现如下方法:

image.png

3、我们通过以上的查找得到了查找流程,那么我们继续去验证一下,通过lldb调试后,结果如下:

[图片上传失败...(image-729895-1624257474342)]

如上图我们能够看到namehomeTownsettergetter方法,但是没有看到我们的类方法和对象方法,我猜测是否是因为没有实现导致呢?然后我们去实现方法,再次运行后发现只有对象方法没有类方法,结果如下:

image.png

4、通过打印发现对象方法是有了,但是没有类方法,那么类方法存储在哪里呢?请听下回分析。

总结

就俩字欧耶

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

推荐阅读更多精彩内容