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

iOS--oc对象,类,和元类本质

iOS--oc对象,类,和元类本质

  • 前言
    • 实例对象的具体结构
      • 自定义类对象的结构
      • 继承关系
    • 类信息的存放
      • 对isa、superclass总结

前言

最近在学习runtime的过程中,发现其中消息发送-动态方法解析-消息转发中涉及到了大量的类与对象的底层知识,看别人博客的时候,别人往往也会先讲一遍oc对象的本质 ;
具体参考了iOS底层原理总结 - 探寻OC对象的本质

实例对象的具体结构

在探讨oc对象的本质前,首先我们要明白oc语言他的底层实现都是c/c++代码 ;

如图:
在这里插入图片描述

oc中的对象,在c/c++中往往以结构体的形式存在 ;
NSobject对象如下:

struct NSObject_IMPL {Class isa;
};
// 查看Class本质
typedef struct objc_class *Class;
我们发现Class其实就是一个指针,对象底层实现其实就是这个样子。

也就是说Nsobject结构体中只存有一个isa指针,至于这个指针指向哪里,其实我们也可以大致猜到了,这个isa指针指向了类(结构体对象);
这里注意一个点,这里的isa指针在arm64架构前是一个单纯的指针,但在arm64架构优化指针后底层是一个联合图或者说共用体(union);这里的isa使用的共用体为了节省空间,不断的进行值覆盖的操作,结合位域可以更大限度的节约内存空间,还不用覆盖旧值 ;
其中的具体细节就不说了,只需要知道优化后的共用体不仅存放这类对象或元类对象地址,还存放了很多额外属性 ;

  • 还有,指针在64位架构中占8个字节;
  • 在Objective-C中,对象的地址确实可以视为指向其内部isa指针的地址,因为isa是对象内存布局的第一个元素。所以,当你说“objc存储的就是isa的地址”,这一点是对的。

自定义类对象的结构

这里用别人的一个例子:

@interface Student : NSObject{@publicint _no;int _age;
}
@end
@implementation Studentint main(int argc, const char * argv[]) {@autoreleasepool {Student *stu = [[Student alloc] init];stu -> _no = 4;stu -> _age = 5;NSLog(@"%@",stu);}return 0;
}
@end

这里中的对象的结构体可以转换为如下的形式:

struct Student_IMPL {Class *isa;int _no;int _age;
};

此结构体占用多少存储空间,对象就占用多少存储空间。因此结构体占用的存储空间为,isa指针8个字节空间+int类型_no4个字节空间+int类型_age4个字节空间共16个字节空间;
具体内存大小的分析记得要内存对齐 ;

那么一个NSObject对象占用多少内存? NSObjcet实际上是只有一个名为isa的指针的结构体,因此占用一个指针变量所占用的内存空间大小,如果64bit占用8个字节,如果32bit占用4个字节。

继承关系

/* Person */
@interface Person : NSObject
{int _age;
}
@end@implementation Person
@end/* Student */
@interface Student : Person
{int _no;
}
@end@implementation Student
@endint main(int argc, const char * argv[]) {@autoreleasepool {NSLog(@"%zd  %zd",class_getInstanceSize([Person class]),class_getInstanceSize([Student class]));}return 0;
}

类对象实质上是以结构体的形式存储在内存中;下面是类对象结构体的图示:
在这里插入图片描述

注意一下,上面的类对象结构体是不完整的,应该是一种简化版本 ;

  • 我们发现只要是继承自NSObject的对象,那么底层结构体内一定有一个isa指针。

那么他们所占的内存空间是多少呢?单纯的将指针和成员变量所占的内存相加即可吗?上述代码实际打印的内容是16 16,也就是说,person对象和student对象所占用的内存空间都为16个字节。
其实实际上person对象确实只使用了12个字节。但是因为内存对齐的原因。使person对象也占用16个字节。

类信息的存放

从实例对象的结构出发,我们知道了它的结构体中只有isa指针和成员变量 ;
instance对象就是通过类alloc出来的对象,每次调用alloc都会产生新的instance对象;

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

  • isa指针
  • 其他成员变量

在这里插入图片描述

既然instance对象结构体中不包括类的方法信息 ;那我们该如何访问类的方法信息 ?

class对象 我们通过class方法或runtime方法得到一个class对象。class对象也就是类对象

每一个类在内存中有且只有一个class对象。

class对象在内存中存储的信息主要包括

  • isa指针
  • superclass指针
  • 类的属性信息(@property),类的成员变量信息(ivar)
  • 类的对象方法信息(instance method),类的协议信息(protocol)

在这里插入图片描述

所以instance对象的isa指针指向class对象来访问方法信息 ;
成员变量的值时存储在实例对象中的,因为只有当我们创建实例对象的时候才为成员变赋值。但是成员变量叫什么名字,是什么类型,只需要有一份就可以了。所以存储在class对象中。

同样的class对象的isa指针指向的是它的元类对象 ;

元类对象 meta-class

在内存中存储的信息主要包括

  • isa指针
  • superclass指针
  • 类的类方法的信息(class method)
    在这里插入图片描述

meta-class对象和class对象的内存结构是一样的,所以meta-class中也有类的属性信息,类的对象方法信息等成员变量,但是其中的值可能是空的。
class的isa指向meta-class 当调用类方法时,通过class的isa找到meta-class,最后找到类方法的实现进行调用

1.当对象调用实例方法的时候,我们上面讲到,实例方法信息是存储在class类对象中的,那么要想找到实例方法,就必须找到class类对象,那么此时isa的作用就来了。

2.当类对象调用类方法的时候,同上,类方法是存储在meta-class元类对象中的。那么要找到类方法,就需要找到meta-class元类对象,而class类对象的isa指针就指向元类对象

3.当对象调用其父类对象方法的时候,又是怎么找到父类对象方法的呢?,此时就需要使用到class类对象superclass指针。
当Student的instance对象要调用Person的对象方法时,会先通过isa找到Student的class,然后通过superclass找到Person的class,最后找到对象方法的实现进行调用,同样如果Person发现自己没有响应的对象方法,又会通过Person的superclass指针找到NSObject的class对象,去寻找响应的方法

这里我猜想可能与runtime的消息流程有关,先从isa指针寻找,找不到就寻找到父类中,然后从父类的isa指针寻找 ;下面寻找父类的类方法也是一样的 ;

4.当类对象调用父类的类方法时,就需要先通过isa指针找到meta-class,然后通过superclass去寻找响应的方法

在这里插入图片描述

对isa、superclass总结

  • instance的isa指向class
  • class的isa指向meta-class
  • meta-class的isa指向基类的meta-class,基类的isa指向自己
  • class的superclass指向父类的class,如果没有父类,superclass指针为nil
  • meta-class的superclass指向父类的meta-class,基类的meta-class的superclass指向基类的class
  • instance调用对象方法的轨迹,isa找到class,方法不存在,就通过superclass找父类
  • class调用类方法的轨迹,isa找meta-class,方法不存在,就通过superclass找父类

相关文章:

  • 爬虫-电影影评爬取
  • 英语翻译人工翻译优势
  • Ubuntu server 24 (Linux) Zabbix 7.0 LTS 配置mail邮件报警
  • 线程池前置知识
  • Qt第一次作业
  • uniapp怎么实现条形码
  • boot整合solr
  • Python API自动化:提升开发效率的利器
  • 提升营业厅服务质量:DuDuTalk柜台录音设备,台席质检的新选择
  • PostgreSQL:在CASE WHEN语句中使用SELECT语句
  • vs2019 c++20规范 STL 库中头文件 <atomic> 源码注释及探讨几个知识点
  • C#面:abstract 的 method 是否可同时是 static,是否可同时是 native,是否可同时是 synchronized?
  • 树莓派4B_OpenCv学习笔记6:OpenCv识别已知颜色_运用掩膜
  • Vulnhub-DC-8
  • 表面温度测量方法有哪些?常见方法解析
  • [deviceone开发]-do_Webview的基本示例
  • exif信息对照
  • Java深入 - 深入理解Java集合
  • oldjun 检测网站的经验
  • opencv python Meanshift 和 Camshift
  • PHP CLI应用的调试原理
  • python 装饰器(一)
  • Python中eval与exec的使用及区别
  • Service Worker
  • spring + angular 实现导出excel
  • Spring声明式事务管理之一:五大属性分析
  • 聊一聊前端的监控
  • 时间复杂度与空间复杂度分析
  • 树莓派用上kodexplorer也能玩成私有网盘
  • ​iOS实时查看App运行日志
  • # windows 安装 mysql 显示 no packages found 解决方法
  • #LLM入门|Prompt#1.7_文本拓展_Expanding
  • #微信小程序(布局、渲染层基础知识)
  • (3) cmake编译多个cpp文件
  • (3)医疗图像处理:MRI磁共振成像-快速采集--(杨正汉)
  • (bean配置类的注解开发)学习Spring的第十三天
  • (第9篇)大数据的的超级应用——数据挖掘-推荐系统
  • (附源码)ssm捐赠救助系统 毕业设计 060945
  • (接口自动化)Python3操作MySQL数据库
  • (解决办法)ASP.NET导出Excel,打开时提示“您尝试打开文件'XXX.xls'的格式与文件扩展名指定文件不一致
  • (强烈推荐)移动端音视频从零到上手(下)
  • (数据大屏)(Hadoop)基于SSM框架的学院校友管理系统的设计与实现+文档
  • (一) 初入MySQL 【认识和部署】
  • (转)Sublime Text3配置Lua运行环境
  • ***详解账号泄露:全球约1亿用户已泄露
  • **《Linux/Unix系统编程手册》读书笔记24章**
  • *ST京蓝入股力合节能 着力绿色智慧城市服务
  • .Net 6.0 Windows平台如何判断当前电脑是否联网
  • .NET CF命令行调试器MDbg入门(三) 进程控制
  • .net core webapi Startup 注入ConfigurePrimaryHttpMessageHandler
  • .net 调用海康SDK以及常见的坑解释
  • .Net 中的反射(动态创建类型实例) - Part.4(转自http://www.tracefact.net/CLR-and-Framework/Reflection-Part4.aspx)...
  • .NET开源项目介绍及资源推荐:数据持久层 (微软MVP写作)
  • @font-face 用字体画图标
  • @GlobalLock注解作用与原理解析