[iOS]类和对象的底层原探索

[iOS]类和对象的底层探索

文章目录

  • [iOS]类和对象的底层探索
    • 继承链(类,父类,元类)
      • instance 实例对象
      • class 类对象
      • meta-class 元类对象
    • 对对象、类、元类和分类的探索
      • instance 实例对象
      • class 类对象
      • meta-class 元类对象
      • 分类(category)
    • isMemberOfClass & isKindOfClass(类和对象)

有些部分前面讲的不是很清楚
或者没讲的很仔细的
在这一篇重新拿出来记录一下
也当作是复习一遍

继承链(类,父类,元类)

先来结论

instance 实例对象

instance对象就是通过alloc方法创建出来的对象,每次调用alloc方法都会生成新的instance对象

instance对象在内存中存放的信息包括

  1. isa指针
  2. 其他成员变量

class 类对象

class对象的作用是用来描述一个instance对象,它内部存放一个类的属性信息(@property)、对象方法信息(instance method)、协议信息(protocol)、成员变量信息(ivar),另外class对象里面还有两个指针,isa指针 和 superclass指针。

Objective-C类是由Class类型来表示的,它实际上是一个指向objc_class结构体的指针

类对象就是一个结构体struct objc_class,这个结构体存放的数据称为元数据(metadata),
该结构体的第一个成员变量也是isa指针,这就说明了Class本身其实也是一个对象,因此我们称之为类对象
类对象在编译期产生用于创建实例对象,是单例

meta-class 元类对象

meta-class对象的作用是用来描述一个class对象

跟class一样,元类对象在内存中也是只有一份的
它内部存储了 isa指针 + superclass指针 + 类方法信息(+方法)

类对象中的元数据存储的都是如何创建一个实例的相关信息
那么类对象和类方法应该从哪里创建呢? 就是从isa指针指向的结构体创建
类对象的isa指针指向的我们称之为元类(metaclass), 元类中保存了创建类对象以及类方法所需的所有信息

来看这张很经典的图
在这里插入图片描述
通过上图我们可以看出整个体系构成了一个自闭环,struct objc_object结构体实例它的isa指针指向类对象
类对象的isa指针指向了元类,super_class指针指向了父类的类对象
而元类的super_class指针指向了父类的元类,那元类的isa指针又指向了自己

为什么要有元类?

元类(Meta Class)是一个类对象的类。在上面我们提到,所有的类自身也是一个对象,我们可以向这个对象发送消息(即调用类方法)。为了调用类方法,这个类的isa指针必须指向一个包含这些类方法的一个objc_class结构体。这就引出了meta-class的概念,元类中保存了创建类对象以及类方法所需的所有信息。任何NSObject继承体系下的meta-class都使用NSObject的meta-class作为自己的所属类,而基类的meta-class的isa指针是指向它自己

OC的类其实也是一个对象
一个对象就要有一个它属于的类
意味着类也要有一个isa指针指向其所属的类
那么类的类是什么
就是我们所说的元类(MetaClass)
所以,元类就是类的所属类

既然元类是个类那元类的类是什么呢?
所有的元类都使用根元类作为他们的类
根元类的 isa 指针指向了它自己

对对象、类、元类和分类的探索

法一

可以打开前面提到过的runtime源码查看相关定义

法二

将OC代码转换为C/C++代码之后再对其进行研究

法二可行的理由是因为OC本质底层实现转化其实都是C/C++代码

咱们今天先通过法二进行探索 同时学习一下转化OC代码的操作

gcc -rewrite-objc main.m

使用上面指令可以转化OC代码
但是如果你的代码内引用了UIKit之类的库
那么会出现这么一条报错
请添加图片描述
好 怎么办呢

可以通过加一些参数指定使用 iOS 系统的 SDK

gcc -rewrite-objc -fobjc-arc -fobjc-runtime=ios-13.0.0 -isysroot/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator13.0.sdk main.m

也可以通过xcrun工具
Xcode安装的时候顺带安装了xcrun命令,xcrun命令在clang的基础上进⾏了⼀些封装,要更好⽤⼀些

xcrun -sdk iphoneos gcc -rewrite-objc main.m

instance 实例对象

对于我们的MYObject对象

请添加图片描述
找到它的对应部分
在这里插入图片描述

typedef struct objc_object MYObject;
这里使用 typedef 为 struct objc_object 结构体定义了一个别名 MYObject
所以对象本质上就是结构体

typedef struct {} _objc_exc_MYObject;
同样是使用 typedef 定义了一个名为 _objc_exc_MYObject 的结构体类型,不过这里的结构体没有具体的成员。

struct MYObject_IMPL {...};
这是定义了一个名为 MYObject_IMPL 的结构体。该结构体包含了另一个结构体 NSObject_IMPL NSObject_IVARS,以及两个 NSString * 类型的成员变量 _age 和 _Nonnull _name

“IMPL”常见的是“implementation”的缩写,意思是“实现”。在编程中,它通常用于表示某个类、方法或功能的具体实现部分。例如,可能会有一个接口(interface)定义了一些方法,而“IMPL”后缀的类则是具体实现这些方法的类。

class 类对象

我们接着找到NSObject_IMPL的定义部分
在这里插入图片描述

关于NSObject的具体实现部分只有一个Class类型的isa指针
那么再看看Class类型到底是何方神圣

在这里插入图片描述

可以看出Class底层是objc_class *类型
咱们前面讲过objc_class结构体本质上代表了类对象

由此回收第一个结论

类对象中的元数据存储的都是如何创建一个实例的相关信息

同时发现

  • id底层实现是struct objc_object *类型,定义为了一个Class的指针。
  • SEL是struct objc_selector *类型
  • IMP是void (*)(void)函数指针类型

接下来我们要找objc_class *的定义
但是在这个转化的文件内 找不到objc_class *的定义了
好 开始挑源码来看

定义了洋洋洒洒五百行

方法存储在公共内存位置,提供给所有的实例对象使用、类对象使用

所以咱除去方法,主要看看其成员变量

struct objc_class : objc_object {// Class ISA;Class superclass;cache_t cache;             // formerly cache pointer and vtableclass_data_bits_t bits;    // class_rw_t * plus custom rr/alloc flags
}

好 可能和大家在其他博客内看到的不太一样
上面是在2006年苹果发布Objc 2.0之后,objc_class的定义
之前的长这样

struct objc_class {Class isa  OBJC_ISA_AVAILABILITY;#if !__OBJC2__Class super_class                                        OBJC2_UNAVAILABLE;const char *name                                         OBJC2_UNAVAILABLE;long version                                             OBJC2_UNAVAILABLE;long info                                                OBJC2_UNAVAILABLE;long instance_size                                       OBJC2_UNAVAILABLE;struct objc_ivar_list *ivars                             OBJC2_UNAVAILABLE;struct objc_method_list **methodLists                    OBJC2_UNAVAILABLE;struct objc_cache *cache                                 OBJC2_UNAVAILABLE;struct objc_protocol_list *protocols                     OBJC2_UNAVAILABLE;
#endif} OBJC2_UNAVAILABLE;

分析一下

superclass 指针指向该类的父类,通过它可以建立类的继承关系。
cache(以前是缓存指针和虚函数表)用于存储方法等的缓存信息,以提高方法查找速度。
bits 通过位运算等方式存储和管理了一些相关信息及自定义标志,例如类的读写权限、是否有自定义的内存分配标志等。

虽然这些成员变量本身并不直接包含所有的类信息,但通过它们以及与之相关的各种方法和运行时机制,可以访问到类的其他详细信息,如属性、方法列表、协议列表等。这些信息可能通过间接的方式存储、计算或在运行时动态获取。

将源码的定义转化为类图

在这里插入图片描述

NSObject本质上是一个叫做NSObject_IMPL的结构体
其成员变量isa本质上也是一个指向objc_class结构体的指针(objc_class继承自objc_object结构体,内部有一个isa成员)

meta-class 元类对象

其实元类和类的本质都是objc_class结构体,只不过它们的用途不一样,类的methods成员变量里存储着该类所有的实例方法信息,而元类的methods成员变量里存储着该类所有的类方法信息

分类(category)

分类是OC的一个高级特性,我们一般用它给系统的类或者第三方库的类扩展方法,属性和协议,或者把一个类不同功能分散到不同的模块中实现
来看看它的源码


同样属于结构体

说法一
可以看到里面是没有成员变量列表存在的,这样可以解释分类为什么不能添加成员变量
说法二
因为category是运行时添加的,他只能操作class_rw_t结构,但是class_rw_t结构没有添加成员变量的入口。成员变量是存储在class_ro_t中的,是无法被修改的,所以category就不能添加成员变量。

在 Objective-C 中,分类(category)不能添加成员变量。这是因为分类在运行时动态添加,它只能操作 class_rw_t 结构,而 class_rw_t 结构没有提供添加成员变量的入口。成员变量的信息是存储在 class_ro_t 中的,并且在运行时是不可修改的。第一种说法不准确,不能仅仅因为源码中分类的定义没有成员变量列表就得出分类不能添加成员变量的结论,关键在于运行时的结构和机制限制

哦对了要注意有一个点

分类中的方法与原始类以及父类方法相比具有更高优先级,如果覆盖父类的方法,可能导致super消息的断裂。因此,最好不要覆盖原始类中的方法

我们知道一个类的实例方法存储在类里面,所有的类方法在元类里面,而对象调用方法isa指针先到相应的类的和元类,然后在其方法列表中查找
那么分类是这么实现这一功能的?

留个悬念 这周进度不够了 我得先写其他博客

isMemberOfClass & isKindOfClass(类和对象)

老规矩 上总结

图解isKindOfClass和isMemberOfClass方法

总结

当调用者是——类对象SubClass

+(BOOL)isKindOfClass:(Class)cls :

判断cls是不是 元类->父类的元类->父父类的元类->…->根元类->NSObject (元类的superclass继承链)其中一个。
cls 传除NSObject.class外的任意类对象均为false。

+(BOOL)isMemeberOfClass:(Class)cls :

判断cls是不是自己的isa,

当调用者是——元类MetaClass

+(BOOL)isKindOfClass:(Class)cls :

判断cls是不是 根元类->NSObject 中的任意一个

+(BOOL)isMemeberOfClass:(Class)cls :

判断cls是不是根元类

当调用者是——对象Obj

-(BOOL)isKindOfClass:(Class)cls :

判断cls是不是类对象->父类->…->NSObject(superclass继承链)其中一个

-(BOOL)isMemeberOfClass:(Class)cls :

判断 cls 是不是自己的 类(类对象)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/46120.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

防火墙之带宽管理篇

核心思想 1.带宽限制:限制非关键业务流量占用带宽的比例 2.带宽保证:保证关键的业务流量传输不受影响。业务繁忙时,确保业务不受影响。 3.限制连接数:可以针对某些业务进行连接数的限制,首先可以降低该业务占用带宽…

基于UltraFace的人脸检测在地平线旭日X3派上的部署和测试(Python版本和C++版本)

电脑端的测试环境搭建 如果不想再搭建环境和测试代码bug上浪费更多的时间可以直接获取本人的测试虚拟机,所有的测试代码、虚拟环境和板端测试工程以全部打包到了虚拟机,需要的可以通过网盘获取: 代码和虚拟机百度网盘链接: 链接…

【AI绘画教程】Stable Diffusion 1.5 vs 2

在本文中,我们将总结稳定扩散 1 与稳定扩散 2 辩论中的所有要点。我们将在第一部分中查看这些差异存在的实际原因,但如果您想直接了解实际差异,您可以跳下否定提示部分。让我们开始吧! Stable Diffusion 2.1 发布与1.5相比,2.1旨在解决2.0的许多相对缺点。本文的内容与理解…

网络和安全操作

一、编辑文件 文本编辑器有很多,比如图形模式的gedit、OpenOffice 等,文本模式下的编辑器有vi、vim(vi的增强版本)等。vi和vim是我们在Linux中最常用的编辑器。 gedit:类似于windows下的记事本,很方便的去…

AI 大事件:超级明星 Andrej Karpathy 创立AI教育公司 Eureka Labs

🧠 AI 大事件:超级明星 Andrej Karpathy 创立AI教育公司 Eureka Labs 摘要 Andrej Karpathy 作为前 OpenAI 联合创始人、Tesla AI 团队负责人,他的专业性和实力备受瞩目。Karpathy 对 AI 的普及和教育充满热情,从 YouTube 教程到…

两年经验前端带你重学前端框架必会的ajax+node.js+webpack+git等技术 Day1

黑马程序员前端AJAX入门到实战全套教程,包含学前端框架必会的(ajaxnode.jswebpackgit),一套全覆盖 Day1 你好,我是Qiuner. 为帮助别人少走弯路和记录自己编程学习过程而写博客 这是我的 github https://github.com/Qiuner ⭐️ ​…

【算法/天梯赛训练】天梯赛模拟题集

L1-009 N个数求和 #include <iostream> #include <algorithm>using namespace std;typedef long long ll; const int N 105;typedef struct node {ll x, y; }node; node a[N];ll gcd(ll a, ll b) {return b ? gcd(b, a % b) : a; }int main() {int n;cin >>…

《昇思25天学习打卡营第25天|第9天》

今天是打卡的第九天&#xff0c;今天学习的是使用静态图加速这门课程&#xff0c;从他的背景学起&#xff1a;AI编译框架分为两种运行模式&#xff0c;分别是动态图模式和静态图模式&#xff0c;动态图模式特点&#xff1a;计算图的构建和计算同时发生&#xff0c;缺点&#xf…

如何追查一个packet在linux 系统哪里丢失

要想追一个包在系统哪里丢失了&#xff0c; 就要了解 一个应用层的包在送出时 要经历那些 检查点 和被丢掉的点。 1. 在传输层&#xff0c;如果是 tcp 包 会有contrack 的 buf 的限制 可能会导致 packets 的丢失。 > 检查办法&#xff1a;查看dmesg日志有报错&#xff1a;k…

MySQL数据库慢查询日志、SQL分析、数据库诊断

1 数据库调优维度 业务需求&#xff1a;勇敢地对不合理的需求说不系统架构&#xff1a;做架构设计的时候&#xff0c;应充分考虑业务的实际情况&#xff0c;考虑好数据库的各种选择(读写分离?高可用?实例个数?分库分表?用什么数据库?)SQL及索引&#xff1a;根据需求编写良…

Java Web常见框架寻找路由技巧

在Java Web代码审计中&#xff0c;寻找和识别路由是很关键的部分。通过注册的路由可以找到当前应用对应的Controller&#xff0c;其作为MVC架构中的一个组件&#xff0c;可以说是每个用户交互的入口点。简单介绍下Java Web中常见框架&#xff08;Spring Web、Jersey&#xff09…

十、Java集合 ★ ✔(模块18-20)【泛型、通配符、List、Set、TreeSet、自然排序和比较器排序、Collections、可变参数、Map】

day05 泛型,数据结构,List,Set 今日目标 泛型使用 数据结构 List Set 1 泛型 1.1 泛型的介绍 ★ 泛型是一种类型参数&#xff0c;专门用来保存类型用的 最早接触泛型是在ArrayList&#xff0c;这个E就是所谓的泛型了。使用ArrayList时&#xff0c;只要给E指定某一个类型…

【Vue3】4个比较重要的设计模式!!

大家好,我是CodeQi! 一位热衷于技术分享的码仔。 在我投身于前端开发的职业生涯期间,曾有一次承接了一个大型项目的维护工作。此项目运用的是 Vue 框架,然而其代码结构紊乱不堪,可维护性极度糟糕😫。 这使我深刻领会到,理解并运用 Vue 中的重要设计模式是何等关键! …

对LinkedList ,单链表和双链表的理解

一.ArrayList的缺陷 二.链表 三.链表部分相关oj面试题 四.LinkedList的模拟实现 五.LinkedList的使用 六.ArrayList和LinkedList的区别 一.ArrayList的缺陷: 1. ArrayList底层使用 数组 来存储元素&#xff0c;如果不熟悉可以来再看看&#xff1a; ArrayList与顺序表-CSDN…

一些常见的网络故障

&#x1f4d1;打牌 &#xff1a; da pai ge的个人主页 &#x1f324;️个人专栏 &#xff1a; da pai ge的博客专栏 ☁️宝剑锋从磨砺出&#xff0c;梅花香自苦寒来 ☁️运维工程师的职责&#xff1a;监…

【iOS】——ARC源码探究

一、ARC介绍 ARC的全称Auto Reference Counting. 也就是自动引用计数。使用MRC时开发者不得不花大量的时间在内存管理上&#xff0c;并且容易出现内存泄漏或者release一个已被释放的对象&#xff0c;导致crash。后来&#xff0c;Apple引入了ARC。使用ARC&#xff0c;开发者不再…

BUUCTF逆向wp [HDCTF2019]Maze

第一步 查壳&#xff0c;本题是32位&#xff0c;有壳&#xff0c;进行脱壳。 第二步 这里的 jnz 指令会实现一个跳转&#xff0c;并且下面的0EC85D78Bh被标红了&#xff0c;应该是一个不存在的地址&#xff0c;这些东西就会导致IDA无法正常反汇编出原始代码&#xff0c;也称…

启智畅想火车类集装箱号码识别技术,软硬件解决方案

集装箱号码识别需求&#xff1a; 实时检测车皮号、火车底盘号码、集装箱号码&#xff0c;根据火车类型分为以下三种情况&#xff1a; 1、纯车皮&#xff0c;只检测车皮号&#xff1b; 2、火车拉货箱&#xff08;半车皮&#xff09;&#xff0c;检测车皮号集装箱号码&#xff1b…

如何从0搭建一个Ai智体day01

&#x1f4da;《AI破局行动&#xff5c;AI智能体&#xff08;coze&#xff09;实战手册》&#xff1a; https://d16rg8unadx.feishu.cn/wiki/XQESwHW5HiPFlrkZbkqc0Xp7nEb 说明 这个是授权访问的&#xff0c;想学习加我 微信/ Github:** watchpoints &#x1f4fa;Day1-大圣直播…

玩转HarmonyOS NEXT之常用布局三

轮播&#xff08;Swiper&#xff09; Swiper组件提供滑动轮播显示的能力。Swiper本身是一个容器组件&#xff0c;当设置了多个子组件后&#xff0c;可以对这些子组件进行轮播显示。通常&#xff0c;在一些应用首页显示推荐的内容时&#xff0c;需要用到轮播显示的能力。 针对…