当前位置: 首页 > backend >正文

【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指向

http://www.xdnf.cn/news/7700.html

相关文章:

  • Nginx详细配置说明
  • CSS-5.1 Transition 过渡
  • Dify 快速上手 MCP!Java 搭建 MCP Server 接入 Dify详细实战攻略
  • vue中列表filter方法的作用
  • 深入探讨redis:哨兵模式
  • linux下jenkins部署安装使用
  • 上肢康复机器人设计与临床应用研究
  • 达梦数据库线上体验:高度兼容Oracle语法
  • 家电行业数字化实践案例 | 易趋携手某知名家电集团打造数字化项目管理系统
  • 如何看待镍钯金PCB在当代工业制造中的地位和应用?
  • Python 数据库编程
  • Java 04 API
  • 【信息系统项目管理师】第12章:项目质量管理 - 26个经典题目及详解
  • Agent_Attention线性注意力推导
  • ubuntu terminal 查看opencv 版本,或者其他相关库或者包
  • 【LUT技术专题】DnLUT代码解读
  • UniVLA-香港大学-单系统带导航-2025.5.9-开源
  • 通过两个列表构建字典(python极其详细)
  • Redis哨兵(Sentinel)模式详解:构建高可用Redis架构
  • Oracle RAC ADG备库版本降级方案(19.20 → 19.7)
  • 大模型预训练、微调、强化学习、评估指导实践
  • 学习黑客 TELNET 来龙去脉
  • 5.2.4 wpf中MultiBinding的使用方法
  • 宝塔+fastadmin:给项目添加定时任务
  • Spring Boot 使用 jasypt配置明文密码加密
  • 第6章 C控制语句:循环
  • 攻防世界-题目名称-文件包含
  • MySQL 库的操作 -- 字符集和校验规则,库的增删查改,数据库的备份和还原
  • Java IO流操作
  • Prosys OPC:引领工业互联的OPC UA先锋