【iOS】类结构分析

前言

之前我们已经探索得出对象的本质就是一个带有isa指针的结构体,这篇文章来分析一下类的结构以及类的底层原理。

类的本质

类的本质

我们在main函数中写入以上代码,然后利用clang对其进行反编译,可以得到c++文件

可以看到底层使用Class接收,接下来找到Class的定义

发现Class在底层定义为一个名为objc_class的结构体

查找objc_class,发现它继承自objc_object

可以得出:类的本质是objc_class类型的结构体,objc_class继承自objc_object,因此可以一句话概括------万物皆对象

objc_class&objc_object、objc&NSObject的关系

objc_object和NSObject

为什么说继承自objc_object就满足万物皆对象?

我们看到NSObject的定义

对比objc_object的定义:

仔细比较可以看出:

  • 其实NSObject是objc_object的仿写,和objc_object的定义是一样的,在底层会编译成objc_object

  • 同理NSObject类是OC版本的objc_class

而之前我们学习过的NSObject_IMPL则是NSObject编译到objc_object的中间产物,并且实例伪继承自NSObject_IMPL,正如继承objc_object的皆为对象。

objc_class&objc_object

从上文我们已经得知:objc_class继承自objc_object,我们寻找objc_object的定义

可以找到两个定义:第一个位于objc.h,没有被废除,从编译的main-arm64.cpp中可以看到,使用的这个版本的objc_object。第二个位于objc-privat.h

总结objc_class与objc_object的关系如下:

  • 结构体类型objc_class继承自objc_object类型,其中objc_object也是一个结构体,且有一个isa属性,所以objc_class也拥有了isa属性

  • mian-arm64.cpp底层编译文件中,NSObject中的isa在底层是由Class 定义的,其中class的底层编码来自 objc_class类型,所以NSObject也拥有了isa属性

  • NSObject是一个类,用它初始化一个实例对象objc,objc 满足objc_object的特性(即有isa属性),主要是因为isa 是由 NSObject 从objc_class继承过来的,而objc_class继承自objc_object,objc_object 有isa属性。所以对象都有一个isa,isa表示指向,来自于当前的objc_object

  • objc_object(结构体)是当前的根对象,所有的对象都有这样一个特性objc_object,即拥有isa属性

objc_object 与 对象的关系

  • 所有的对象都是以 objc_object 为模板继承过来的

  • 所有的对象是来自 NSObject(OC),但是真正到底层的是一个objc_object(C/C++)的结构体类型

  • objc_object对象的关系 是 继承关系

总结

所有的对象 + 类 + 元类 都有isa属性

所有的对象都是由objc_object继承来的

简单概括就是万物皆对象,万物皆来源于objc_object,有以下两点结论:

  • 所有以 objc_object为模板创建的对象,都有isa属性

  • 所有以 objc_class为模板创建的类,都有isa属性

在结构层面可以通俗的理解为上层OC与底层的对接:

  • 下层是通过结构体定义的模板,例如objc_class、objc_object

  • 上层是通过底层的模板创建的一些类型,例如TCJPerson

objc_class、objc_object、isa、object、NSObject等的整体的关系,如下图:

类的结构

从objc_class的定义可以得出,类有4个属性:

  • Class ISA:类对象与实例对象一样,同样有isa指针,类对象的isa指针关联着元类,Class本身就是一个指针,占用8字节(这个属性是继承自objc_object的)

  • Class superclass:即类的父类,Class类型,占用8个字节

  • cache_t cache

cache_t是一个结构体,内存长度由所有元素决定:_ bucketsAndMaybeMask是long类型,它是一个指针,占用8字节; mask_t是个uint32_t类型,_ mask占用4字节;因**** occupied和****flags都是uint16_t类型,uint16_t是 unsigned short 的别名,所以_occupied占用2字节;flags占用2字节=>cache_t占用16字节。这个属性其实是用来保存方法缓存的,后续博客中会详细介绍。

  • class_data_bits_t bits

class_data_bits_t bits

class_data_bits_t bits这个属性用来存数据,类的属性和方法就保存在这里。可以看到objc_class中有一个class_rw_t *data()方法。

class_rw_t是在运行时生成的,它在realizeClass中生成,它包含了class_ro_t.它在_objc_init方法中关于dyld的回调的map_images中最终将分类的方法与协议都插入到自己的方法列表、协议列表中.它不包含成员变量列表,因为成员变量列表是在编译期就确定好的,它只保存在class_ro_t中.不过,class_rw_t中包含了一个指向class_ro_t的指针

类的属性方法

类的属性

通过LLDB调试我们可以发现,bits当中存储的信息类型是class_rw_t,是一个结构体类型,在这个结构体中有提供相应的方法去获取属性列表、方法列表等。

通过LLDB进一步调试可以发现,这个属性列表中只有属性,没有成员变量。属性与成员变量的区别就是有没有set、get方法,如果有,则是属性,如果没有,则是成员变量。

除此之外,class_rw_t中有个属性class_ro_t,**class_ro_t是在编译期生成的,它存储了当前类在编译期就已经确定的属性、方法以及协议,它里面没有分类中定义的方法和协议。**而成员变量就存放在ro的ivars里面

总结如下:

  • 通过{}定义的成员变量,会存储在类的bits属性中,通过bits --> data() -->ro() --> ivars获取成员变量列表,除了包括成员变量,还包括属性定义的成员变量

  • 通过@property定义的属性,也会存储在bits属性中,通过bits --> data() --> properties() --> list获取属性列表,其中只包含属性

类的方法

类的实例方法存储在类的bits属性中,通过bits --> methods() --> list获取实例方法列表,参观游客实例方法,方法列表中还包含属性的set方法和get方法,以及系统在底层添加的C++的.cxx_destruct方法。

类的类方法存储在元类的bits属性中,通过元类bits --> methods() --> list获取类方法列表。

结论

  • 成员变量存放在ivar

  • 属性存放在property,同时也会存一份在ivar,并生成setter、getter方法

  • 对象方法存放在类里面

  • 类方法存放在元类里面

补充

类存在几份

由于类的信息在内存中永远只存在一份,所以 类对象只有一份。

isKindOfClass和isMerberOfClass的理解

对于isMerberOfClass,方法会对比元类或类本身与某个类(元类还是类取决于类方法还是实例方法,总之就是取isa指针指向的东西)

而对于isKindOfClass,走以下逻辑:

最后给出一张isa的走位图,实现为superclass指向,虚线为isa指向

相关推荐
HarderCoder6 小时前
iOS 知识积累第一弹:从 struct 到 APP 生命周期的全景复盘
ios
使一颗心免于哀伤16 小时前
《设计模式之禅》笔记摘录 - 21.状态模式
笔记·设计模式
叽哥16 小时前
Flutter Riverpod上手指南
android·flutter·ios
用户092 天前
SwiftUI Charts 函数绘图完全指南
ios·swiftui·swift
YungFan2 天前
iOS26适配指南之UIColor
ios·swift
权咚2 天前
阿权的开发经验小集
git·ios·xcode
用户092 天前
TipKit与CloudKit同步完全指南
ios·swift
法的空间3 天前
Flutter JsonToDart 支持 JsonSchema
android·flutter·ios
侃侃_天下3 天前
最终的信号类
开发语言·c++·算法
_落纸3 天前
三大基础无源电子元件——电阻(R)、电感(L)、电容(C)
笔记