【iOS】类与对象底层探索

news2024/9/21 7:43:23

文章目录

  • 前言
  • 一、编译源码
  • 二、探索对象本质
  • 三、objc_setProperty 源码探索
  • 四、类 & 类结构分析
    • isa指针是什么
    • 类的分析
    • 元类
    • 元类的说明
  • 五、著名的isa走位 & 继承关系图
  • 六、objc_class & objc_object
    • objc_class结构
    • superClass
    • bits
    • class_rw_t
    • class_ro_t
    • ro与rw的区别
    • class_rw_ext_t
    • cache_t结构
  • 总结


前言

这篇文章主要探索OC对象的本质

首先我们需要明白我们平时编写的OC代码,底层实现都是C\C++代码

一、编译源码

首先通过终端利用clangmain.m编译为main.cpp

//1、将 main.m 编译成 main.cpp
clang -rewrite-objc main.m -o main.cpp

//2、将 ViewController.m 编译成  ViewController.cpp
clang -rewrite-objc -fobjc-arc -fobjc-runtime=ios-13.0.0 -isysroot / /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator13.7.sdk ViewController.m

//以下两种方式是通过指定架构模式的命令行,使用xcode工具 xcrun
//3、模拟器文件编译
- xcrun -sdk iphonesimulator clang -arch arm64 -rewrite-objc main.m -o main-arm64.cpp 

//4、真机文件编译
- xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m -o main- arm64.cpp 

二、探索对象本质

我们打开编译好的源文件后找到LGPerson,发现其在底层被编译为struct结构体

//NSObject的定义
@interface NSObject <NSObject> {
    Class isa  OBJC_ISA_AVAILABILITY;
}

//NSObject 的底层编译
struct NSObject_IMPL {
	Class isa;
};

//LGPerson的底层编译
struct LGPerson_IMPL {
	struct NSObject_IMPL NSObject_IVARS; // 等效于 Class isa;
	NSString *_name;
};

LGPerson_IMPL实现结构体中的第一个属性是isa,是继承自NSObject,是伪继承。意味着LGPerson拥有者NSObject中所有成员变量

LGPerson中的第一个属性 NSObject_IVARS 等效于 NSObject中的 isa

这里也许我们会产生一个疑问就是为什么isa的类型是class
根本原因是由于isa 对外反馈的是类信息

总结
因此我们可以得出:

OC对象本质就是结构体
LGPerson中的isa就是继承自NSObject中的isa

三、objc_setProperty 源码探索

除了LGPerson的底层定义,我们发现了属性name还有set与get方法,其中set方法依赖于runtime中的objc_setProperty

我们通过源码来查看一下objc_setProperty的底层实现

在这里插入图片描述

// 定义静态内联函数,用于设置对象的属性值。
static inline void reallySetProperty(id self, SEL _cmd, id newValue, ptrdiff_t offset, bool atomic, bool copy, bool mutableCopy)
{
    // 如果偏移量为0,直接将newValue设置为对象的类(可能用于特殊的目的,如改变对象的动态类型)
    if (offset == 0) {
        object_setClass(self, newValue);
        return;
    }

    // 定义用来保存旧值的变量
    id oldValue;
    // 计算属性值在内存中的实际位置
    id *slot = (id*) ((char*)self + offset);

    // 如果指定了copy标志,则对newValue执行不可变拷贝
    if (copy) {
        newValue = [newValue copyWithZone:nil];
    } 
    // 如果指定了mutableCopy标志,则对newValue执行可变拷贝
    else if (mutableCopy) {
        newValue = [newValue mutableCopyWithZone:nil];
    } 
    // 如果没有指定拷贝,检查newValue是否已经是当前值,如果是,则无需操作;否则,增加newValue的引用计数
    else {
        if (*slot == newValue) return;
        newValue = objc_retain(newValue);
    }

    // 如果不是原子操作,直接更新内存位置的值
    if (!atomic) {
        oldValue = *slot;
        *slot = newValue;
    } 
    // 如果是原子操作,使用锁来保证线程安全
    else {
        spinlock_t& slotlock = PropertyLocks[slot]; // 获取与属性位置相关联的锁
        slotlock.lock();                            // 锁定
        oldValue = *slot;                          // 取出旧值
        *slot = newValue;                          // 设置新值
        slotlock.unlock();                         // 解锁
    }

    // 释放旧值的引用,以防内存泄漏
    objc_release(oldValue);
}

其方法原理就是retain新值->设置新值->释放旧值

四、类 & 类结构分析

本篇章的主要目的是分析 类 & 类的结构,整篇都是围绕一个类展开的一些探索

isa指针是什么

OC是一门面向对象编程的语言,每个对象都是类的实例,同时也被称为实例对象,同时每个对象都有一个isa指针,指向对象所属的类

另外我们打开NSObject源码

@interface NSObject <NSObject> {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wobjc-interface-ivars"
    Class isa  OBJC_ISA_AVAILABILITY;
#pragma clang diagnostic pop
}

由此可知类也是一个对象,简称为类对象

类的分析

首先我们定义两个类

继承自NSObject的类CJLPerson

@interface CJLPerson : NSObject
{
    NSString *hobby;
}
@property (nonatomic, copy) NSString *cjl_name;
- (void)sayHello;
+ (void)sayBye;
@end

@implementation CJLPerson
- (void)sayHello
{}
+ (void)sayBye
{}
@end

继承自CJLPerson的类CJLTeacher

@interface CJLTeacher : CJLPerson
@end

@implementation CJLTeacher
@end

在main中分别用两个定义两个对象:person & teacher

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        //ISA_MASK  0x00007ffffffffff8ULL
        CJLPerson *person = [CJLPerson alloc];
        CJLTeacher *teacher = [CJLTeacher alloc];
        NSLog(@"Hello, World! %@ - %@",person,teacher);  
    }
    return 0;
}

元类

首先我们通过一张图片引入元类
在这里插入图片描述

根据调试过程,我们产生了一个疑问:为什么图中的p/x 0x001d8001000022dd & 0x00007ffffffffff8ULLp/x 0x00000001000022b0 & 0x00007ffffffffff8ULL 中的类信息打印出来都是CJLPerson

  • 0x001d8001000022ddperson对象的isa指针地址,其&后得到的结果是 创建person的类CJLPerson
  • 0x00000001000022b0isa中获取的类信息所指的类的isa的指针地址,即 CJLPerson类的类 的isa指针地址,在Apple中,我们简称CJLPerson类的类为 元类
    所以,两个打印都是CJLPerson的根本原因就是因为元类导致的

元类的说明

下面我们来解释一下什么是元类

首先我们知道对象的isa指向类,同时我们前文也说了类也是一个对象,那么类也有isa指针,类的isa指针指向的就是元类

元类是系统给的,当我们创建类时会自动创建元类,类的归属来自于元类

首先我们之前的博客有分析过元类【iOS】isKindOfClass & isMemberOfClass比较
我们这里引出一个问题,NSObject到底有几个?

在这里插入图片描述
从这张图中我们可以看出根元类NSObject只有一个,这个与我们日常开发的NSObject是同一个吗

我们通过代码来验证一下
在这里插入图片描述

可以看出打印出的地址为同一个,所以NSObject只有一个,即类对象与元类都只有一个,而实例对象可以有很多个

 NSObject *object1 = [[NSObject alloc] init];
 NSObject *object2 = [[NSObject alloc] init];
 // 打印两个实例的内存地址
 NSLog(@"object1: %p", object1);
 NSLog(@"object2: %p", object2);
 // 获取并打印NSObject的类对象
 Class objectClass1 = [object1 class];
 Class objectClass2 = [object2 class];
 NSLog(@"NSObject class: %p. %p", objectClass1, objectClass2);
 // 获取并打印NSObject的元类对象
 Class metaClass1 = object_getClass(objectClass1);
 Class metaClass2 = object_getClass(objectClass2);
 NSLog(@"NSObject meta-class: %p, %p", metaClass1, metaClass2);

[面试题]:类存在几份?

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

五、著名的isa走位 & 继承关系图

在这里插入图片描述
这里的知识之前已经说过了,这里不再赘述

六、objc_class & objc_object

isa的走位我们清楚了,现在我们来讨论一个新问题,为什么对象和类会有isa属性,这里就引出了objc_class & objc_object

首先我们通过之前的源码知道:
NSObject的底层编译是NSObject_IMPL结构体

首先我们知道Classisa指针的类型,是由objc_class定义的类型
objc_class实际上就是Class
在iOS中所有的Class都是以objc_class为模版创建的

我们查看一下objc_class的源码
在这里插入图片描述
我们可以看到objc_class结构体是继承自objc_object

再搜寻objc_object的定义
在这里插入图片描述

【问题】objc_class 与 objc_object 有什么关系?
通过查看源码我们可以得出几点说明:

  • objc_class继承自objc_object类型,其中objc_object也是一个结构体,且有一个isa类型
  • 在cpp底层编译中,isa的类型是Class,class的底层编码来自objc_class,因此NSObject也有了isa属性
  • objc_object(结构体)是当前的根对象,所有的对象都有一个特性objc_object,即拥有isa属性

objc_object 与 对象的关系

  • 是一个继承关系,所有对象都是由objc_object继承过来的

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

总结:
所有对象+类+元类都有isa属性
所有对象都是由objc_object继承来的

objc_class结构

首先我们在源码中找到其结构

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

isa:主要指向类对象或是元类对象
superclass:指向当前类的父类
cache:方法缓存,提高调用方法的性能
bits:封装了类的其他信息,例如成员变量,方法列表,协议,属性

元类对象结构也是这样,只不过元类对象里面存放的是类方法

superClass

这里需要注意superclass是objc_class特有的,实例对象是没有的

bits

类结构中还有一个bits
我们通过源码查看一下其数据结构class_data_bits_t

struct class_data_bits_t {
    friend objc_class;
    // Values are the FAST_ flags above.
    uintptr_t bits;
    public:
    class_rw_t* data() const {
        return (class_rw_t *)(bits & FAST_DATA_MASK);
    }
    // Get the class's ro data, even in the presence of concurrent realization.
    // fixme this isn't really safe without a compiler barrier at least
    // and probably a memory barrier when realizeClass changes the data field
    const class_ro_t *safe_ro() const {
        class_rw_t *maybe_rw = data();
        if (maybe_rw->flags & RW_REALIZED) {
            // maybe_rw is rw
            return maybe_rw->ro();
        } else {
            // maybe_rw is actually ro
            return (class_ro_t *)maybe_rw;
        }
    }
}

其中最重要的两个方法就是datasafe_ro,两个方法分别返回class_rw_tclass_ro_t

class_rw_t

在这里插入图片描述
首先可以看到三个方法,可以分别获取到类的方法,属性,协议

class_ro_t

在这里插入图片描述
可以看到有方法、属性、协议和成员变量。但方法、属性、协议的命名都是base开通的

ro与rw的区别

ro是编译阶段生成,rw是运行阶段生成,从存储的角度来说,ro中有方法,属性,协议与成员变量,而rw中没有成员变量,rw中的取值方法也是通过取ro或是rwe的值来获得的

class_rw_ext_t

在2020WWDC中有个视频对ro与rw进行了解释,由于rw是一块脏内存,但是rw总有许多用不到的数据,我们将rw中那些一般用不到的数据分离出来变为干净的内存,也就是rw_ext_t,这样就减轻了rw对内存的占用

rw_ext_t生成条件:

  • 使用分类的类

  • 使用Runtime API动态修改类的结构的时候

这两种情况时,由于类的结构发生改变,但是ro是只读的,因此需要重新生成可读可写的内存结构rw_ext(Dirty Memory, 比较贵),来存放新的类结构。

由此再次读取方法,属性,列表时如果有rw_ext,就会先从rw_ext中读取,如果没有再去读取ro

参考博客:
iOS八股文(四)类对象的结构(下)

思考:为什么类方法存储在元类中,而不把类方法存储在类对象中?或者说设置元类的目的是什么?

  1. 单一职责设计原理:一个对象或是一个类只应该有一个职责,类对象负责实例对象的行为,例如实例方法,协议,成员变量,属性等,元类对象负责类对象的行为,负责存放类方法,各司其职互不影响
  2. 符合消息转发机制
  3. 继承模型的一致性:在Objective-C中,类方法的继承与实例方法的继承遵循相同的模式。如果没有元类,类方法的继承将需要另外一套机制来处理。

cache_t结构

cache的作用是在objc_msgSend过程中会先在cache中根据方法名来hash查找方法实现,如果能查找到就直接掉用。如果查找不到然后再去rw_t中查找。然后再在cache中缓存。

总结

通过这篇文章我们得知

  1. 所有对象都有isa属性
  2. 所有对象都是由objc_object继承而来的
  3. objc_class中存放着对象的各种信息,实例对象则存放成员变量,类对象则存放实例方法与属性等,元类对象则存放类方法,符合单一职责原则
  4. isa 指针指向对象所属的类
  5. 可以从bits中的rw中查找方法属性,但不能查找到成员变量,成员变量存储在ro中,这也是为什么分类不能添加成员变量的原因

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

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

相关文章

【无法运行 AutoCAD,原因可能如下1) 此版本的 AutoCAD 装不正确】

错误提示如下 打开autoremove&#xff0c;点击扩展&#xff0c;输入 无法运行&#xff0c;点击搜索 如果出现这个提示&#xff0c;请重启电脑再点击一遍此按钮 出现修复成功即可 ​如果没提示修复成功可以联系技术人员。 ps&#xff1a;autoremove每周六登录方式用其他登…

警惕侵权风险,张驰课堂六西格玛培训产品请认准官方平台购买

亲爱的朋友们&#xff1a; 在知识经济的时代&#xff0c;知识产权的保护显得尤为重要。我们深知&#xff0c;知识是创新的源泉&#xff0c;而知识产权则是保障创新成果得以合理运用的重要法律手段。然而&#xff0c;近期我们公司&#xff08;张驰课堂&#xff09;却遭遇了一起…

Web3革命:区块链如何重塑互联网

引言 互联网的发展已经深刻地改变了我们的生活方式&#xff0c;而现在&#xff0c;Web3和区块链技术正在为我们提供一个全新的数字世界的视角。本文将带你深入了解Web3的核心概念、技术特性以及它如何正在重塑我们的互联网体验。 从Web1.0到Web3&#xff1a;数字革命的演进 W…

C++ 面向对象-封装

C 是一种多范式编程语言&#xff0c;它支持面向对象编程&#xff08;OOP&#xff09;范式。面向对象编程是一种程序设计思想&#xff0c;其中程序由对象组成&#xff0c;每个对象都是一个实例&#xff0c;具有数据和相关操作。在C中&#xff0c;实现面向对象编程主要通过类和对…

物理机中没有VMNet1和VMNet8虚拟网卡

控制面板——网络连接——网络适配器 VMware Network Adapter VMnet1 VMware Network Adapter VMnet8 如果没有这两个虚拟网卡&#xff0c;虚拟机的网络会出现问题 # 解决办法-恢复虚拟网卡默认设置 1、下载并打开ccleaner&#xff0c;ccleaner官网&#xff1a;CCleaner M…

jdbc操作数据库 and 一个商品管理页面

文章目录 1. 介绍1.1 应用知识介绍1.2 项目介绍 2. 文件目录2.1 目录2.2 介绍以下&#xff08;从上到下&#xff09; 3. 相关代码3.1 DBConnection.java3.2 MysqlUtil.java3.3 AddServlet.java3.4 CommodityServlet.java3.5 DelectServlet.java3.6 SelectByIdServlet.java3.7 S…

2024 OceanBase 开发者大会:OceanBase 4.3正式发布,打造PB级实时分析数据库

4月20日&#xff0c;2024 OceanBase开发者大会盛大召开&#xff0c;吸引了50余位业界知名的数据库专家和爱好者&#xff0c;以及来自全国各地的近600名开发者齐聚一堂。他们围绕一体化、多模、TP与AP融合等前沿技术趋势展开深入讨论&#xff0c;分享场景探索的经验和最佳实践&a…

同旺科技 USB TO SPI / I2C适配器读写24LC256--字节写

所需设备&#xff1a; 1、USB 转 SPI I2C 适配器&#xff1b;内附链接 2、24LC256芯片 适应于同旺科技 USB TO SPI / I2C适配器升级版、专业版&#xff1b; 00地址写入一个字节数据AA&#xff0c;并读回验证&#xff1b; 单字节写时序&#xff1a; 读字节时序&#xff1a; …

架构师系列-MYSQL调优(七)- 索引单表优化案例

索引单表优化案例 1. 建表 创建表 插入数据 下面是一张用户通讯表的表结构信息,这张表来源于真实企业的实际项目中,有接近500万条数据. CREATE TABLE user_contacts (id INT(11) NOT NULL AUTO_INCREMENT,user_id INT(11) DEFAULT NULL COMMENT 用户标识,mobile VARCHAR(50…

2016年新华三杯复赛实验试题

2016年新华三杯复赛实验试题 拓扑图 配置需求 考生根据以下配置需求在 HCL 中的设备上进行相关配置。 以太网接口配置 将 S1、S2 的以太网接口 G1/0/1 至 G1/0/16 的模式用命令 combo enable copper 激活为电口。 虚拟局域网 为了减少广播&#xff0c;需要规划并配置 VLA…

Zabbix监控系统:基础配置及部署代理服务器

目录 前言 一、自定义监控内容 1、在客户端创建自定义key 2、在服务端验证新建的监控项 3、在web界面创建自定义监控项模版 3.1 创建模版 3.2 创建应用集&#xff08;用于管理监控项&#xff09; 3.3 创建监控项 3.4 创建触发器 3.5 创建图形 3.6 将主机与模板关联…

利用selenium发挥vip残存的价值

历史版本谷歌浏览器驱动下载地址 https://chromedriver.storage.googleapis.com/index.html 找到与你电脑当前谷歌浏览器版本一致的驱动然后下载下来(大版本一致即可)。我本地版本是 99.0.04844.51 我这里把 chromedriver 放到 /usr/local/bin 下面了。 启动测试窗口 这里需要…

【软件测试】认识测试|测试岗位|软件测试和开发的区别|优秀的测试人员需要具备的素质

一、什么是测试 测试在⽣活中处处可⻅ 1.生活中的测试场景 案例⼀&#xff1a;对某款购物软件进⾏测试 *启动测试&#xff1a;点击软件图标&#xff0c;测试软件是否可以正常打开 搜索测试&#xff1a;点击输入框&#xff0c;输入关键词&#xff0c;点击搜索 商品测试&#…

【Linux】IO多路转接技术Epoll的使用

【Linux】IO多路转接技术Epoll的使用 文章目录 【Linux】IO多路转接技术Epoll的使用前言正文接口介绍工作原理LT模式与ET模式边缘触发&#xff08;ET&#xff09;水平触发&#xff08;LT&#xff09; 理解ET模式和非阻塞文件描述符ET模式epoll实现TCP服务器简单地封装epoll系统…

python创建线程和结束线程

&#x1f47d;发现宝藏 前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。【点击进入巨牛的人工智能学习网站】。 python创建线程和结束线程 在 Python 中&#xff0c;线程是一种轻量级的执行单元&#xff…

【C++学习】STL之空间配置器之一级空间配置器

文章目录 &#x1f4ca;什么是空间配置器✈STL 提供六大组件的了解&#x1f440;为什么需要空间配置器&#x1f44d;SGI-STL空间配置器实现原理&#x1f302;一级空间配置器的实现 &#x1f4ca;什么是空间配置器 空间配置器&#xff0c;顾名思义就是为各个容器高效的管理空间…

“五之链”第十六期沙龙活动在呆马科技成功举办

2024年4月19日&#xff0c;由临沂呆码区块链网络科技有限公司&#xff08;呆马科技&#xff09;承办的第十六期“五之链”物流主题沙龙活动成功举办。此次活动邀请了政府相关部门、知名科研院所、物流企业等20余家单位参与&#xff0c;共同探讨物流数据要素流通与智能应用的发展…

C语言----链表

大家好&#xff0c;今天我们来看看C语言中的一个重要知识&#xff0c;链表。当然大家可以先从名字中看出来。就是一些表格用链子连接。那么大家是否想到了我们以前学的数组&#xff0c;因为数组也是相连的呀。是吧。但是链表与数组还是有区别的&#xff0c;那么链表是什么有什么…

uniApp项目总结

前言 大半年的时间&#xff0c;项目从秋天到春天&#xff0c;从管理后台到APP再到数据大屏&#xff0c;技术栈从vue3到uniApp再到nuxt3&#xff0c;需求不停的改&#xff0c;注释掉代码都快到项目总体的三分之一。 一&#xff0c;项目技术栈分析 1.1 项目框架 当前&#xf…

树与二叉树的学习笔记

树与二叉树 在之前的学习中&#xff0c;我们一直学习的是一个线性表&#xff0c;数组和链表这两种都是一对一的线性表&#xff0c;而在生活中的更多情况我们要考虑一对多的情况&#xff0c;这时候就引申出了我的新的数据结构那就是树&#xff0c;而树经过一些规矩的指定也就成为…