iOS底层-类的三顾茅庐(一)

2023-05-16

前言

了解完对象的底层,知道isa指向的是类对象。那么类(Class)的本质究竟是什么?本文顺序isa的指向,探索类的继承链,和类对象的结构,并且尝试获取方法和变量的存放位置。

类的继承链

实例对象存储的isa指针,占8字节,并指向所属的类。这里通过3中不同的方式打印一下isa内容:

image-20220426105206333

可以看到都是同个对象,这说明类对象有且仅有一个。它是Class类型的。

isa的指向

在源码中搜索一下:实际上类对象就是objc_class这么一个结构体。

/// An opaque type that represents an Objective-C class.
typedef struct objc_class *Class;

那么为什么说类也是个对象?你看他有isa指针,还继承了objc_object,这结构体同时也是对象的本质。

image-20220426105520234

这就说明和对象一样了,不同的是,类对象的isa指针指向哪里呢?元类对象。接下来是证明。

首先获得类对象:

image-20220426110710263

接着用x/4gx打印类对象的内存地址,以及它的isa地址并打印:发现还是原来那个类???

image-20220426111213190

这打破了前面的认知,是类对象不唯一?其实这就是FFGoods的元类。捋一下,实例对象的isa指向类对象,类对象的isa指向元类对象

元类也是一个objc_class,也有一个isa指针;继续打印看看:

image-20220426111723331

这和我们认识的NSObeject,是不是一回事呢?我再通过p/x NSObject.class打印一下地址,发现并不一样:

image-20220426111856107

继续通过 x/4gx 打印NSObject的类对象及其isa指向的类对象:

image-20220426112302569

最终这个和前面元类的地址一样,这就是对象根类NSObject的元类,也叫根元类(它还是NSObject)。

捋一捋:元类对象的isa指向根元类,根类NSObject的指针也指向根元类

那么根元类有没有isa,有的话指向哪里?没有什么是LLDB不能打印的:

image-20220426112818556

原来,根元类的isa是指向自身isa不是应该包含对象相关信息吗,这里怎么直接等于自身内存地址了?

isa是一个指针只是用来存储内存地址,根元类或者元类没有引用计数,或者是否被弱引用。所以他们两的isa不是上篇文章说到的nonPointerIsa

这样一来,isa的指向形成了一个闭环

那么每个对象都能通过4个步骤找到根元类吗?先卖个关子,往下看。

类的继承

如果创建一个类,不继承NSObject?看个栗子:

image-20220426132421495

发现对于子类FFToys来说,元类的父类 = 父类的元类?也就是说元类之间也保持继承关系

接着看一下根类和根元类有没有父类:

image-20220426132746782

根类NSObject没有父类,NSObject元类的父类竟然是NSObject的类对象。

再补充一下类对象之间的关系:子类的类对象的父类 = 父类的类对象

image-20220426135036789

走位图

由此总结一下继承链,以自定义的父子类为例:

image-20220426133851194

NSObjectobjc的根类,所以它没有父类。根元类的父类都是NSObject

再总结一下isa的走向:

image-20220426135449380

类对象的结构

已知对象的本质是objc_object结构体,那么看一下类对象:其结构体objc_class也是继承objc_object

image-20220426140127546

superclass是指向父类对象的指针,cache我打算之后的文章里单独讲。接下来打算读bits这个内存空间,这里涉及到内存平移的概念。

内存平移

开发中用到的指针,在内存中也需要有个地址来存放指针;举个栗子:

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // 数组指针
        int c[4] = {5,6,7,9};
        int *d = c;
        NSLog(@"数组头指针:%p", &c);
        NSLog(@"通过下标获取元素的内存地址:%p - %p - %p - %p", &c[0], &c[1], &c[2], &c[3]);
        NSLog(@"通过地址偏移获取元素的内存地址:%p - %p - %p - %p", d, d + 1, d + 2, d + 3);
      
        for (int i = 0; i < 4; i++) {
            int value = *(d + i);
            NSLog(@"%d", value);
        }
    }
    return 0;
}

一般将c[4]看做数组指针,c指向数组的首地址;

通过*(d+i)就是一种内存平移。数组里存的是指针,取出第i个指针。

image-20220426143232935

根据声明的int类型,每个指针在内存上相差4字节。

类信息的读写

再看类对象的首地址,通过内存平移32字节(isa + superclass +cache的大小)就拿到bits地址(指针类型class_data_bits_t *

image-20220426150408474

看到$3的这一串数字,显然我不知道它是啥。回顾它的结构体:

image-20220426150714291

friend关键字难道是朋友关系?如果要访问一个类的私有成员,正常需要public关键字。c++提供的friend修饰符,和objc_class成为朋友。使其可以访问里面的私有数据;

参考:C++友元函数和友元类(C++ friend关键字)。友元类中的所有成员函数都是另外一个类的友元函数。例如将类 B 声明为类 A 的友元类,那么类 B 中的所有成员函数都是类 A 的友元函数,可以访问类 A 的所有成员,包括 public、protected、private 属性的。此时,B是objc_class,那么可以访问class_data_bits_t的所有成员。

注意:友元的关系是单向的、不能传递的。

调用一下公开的data()方法:

image-20220426152900632

要看懂这些,还得翻一下class_rw_t源码:看到methods()应该就是方法列表:

image-20220426153208934

一步步拿到方法列表的结构体,但是方法的数量对不上?属性namegetset,以及实例的init方法加起来应该是3个。

image-20220426161307355

跳转method_array_t


DECLARE_AUTHED_PTR_TEMPLATE(method_list_t)

class method_array_t : 
    public list_array_tt<method_t, method_list_t, method_list_t_authed_ptr>
{
    typedef list_array_tt<method_t, method_list_t, method_list_t_authed_ptr> Super;

 public:
    method_array_t() : Super() { }
    method_array_t(method_list_t *l) : Super(l) { }

    const method_list_t_authed_ptr<method_list_t> *beginCategoryMethodLists() const {
        return beginLists();
    }
    
    const method_list_t_authed_ptr<method_list_t> *endCategoryMethodLists(Class cls) const;
};

跳转method_list_t

image-20220426155837849

原来是通过entsize_list_tt这个模板生成的:

image-20220426164021369

在编程的世界里,是容器(container)都有迭代器(iterator),可以用来遍历这个容器。

根据上图的Element& get(uint32_t i),试试get方法,但是拿到的对象不显示具体内容:

image-20220426161528777

找到method_t,对于一个方法,应该有sel方法名,还有imp方法实现。

image-20220426161734169

这里面的big结构体正好就有我们需要的内容,接下来分别获取方法和属性列表。

image-20220426163747609

大小端

这里看到读取的big的公开方法:

image-20220426163307450

有个isSmall()的判断是什么?iOS系统是分大小端,intel电脑是大端模式,arm架构的是小端模式。

**大端:**数据的高位字节存储在内存的低地址端,低位字节存储在内存的高地址端。

**小端:**数据的低位字节存储在内存的低地址端,高位字节存储在内存的高地址端。

举个例子,比如,我们要存储一个16进制数0x12345678,从内存地址0x1001开始存放。内存最低操作单元是字节,每个内存地址存放1字节的大小,而16进制数的2位等于1字节,所以总共要4个内存地址存放(地址低的是高位)。

// 1字节 = 8位(二进制),
// 而一位16进制转二进制是4位,例如 15 = 0xf = 1111 = 8 + 4 + 2 + 1

具体内容:

内存地址大端模式存放小端模式存放
0x10010x120x78
0x10020x340x56
0x10030x560x34
0x10040x780x12

用大端系统下的计算器表示:

image-20220426180303617

换算:

0001 0010 = 0x12; 
0011 0100 = 0x34; 
0101 0110 = 0x56; 
0111 1000 = 0x78;

实例方法

(大端设备)逐个读取方法:

image-20220426163455388

这多出来的.cxx_destruct是什么?我搜到的是析构方法,在ARC模式下用于释放成员变量的。当类拥有成员变量的时候,就会自带这个方法。包括属性自动生成的成员变量也算。

类方法

类对象里只有实例方法,类方法应该是存放在元类里。

接着就通过获取元类的class_rw_t结构中的方法列表来验证一下。

image-20220427175131638

可以看到元类的方法列表里只有这个类方法。

图中涉及的指令:

// x/6gx meta
// p/x (class_data_bits_t *)0x100008220
// p *$1
// p $2.data()
// p *$3
// p $4.methods()
// p $5.list
// p $6.ptr
// p *$7
// p $8.get(0).big()

属性

同理可得属性列表。

class property_array_t : 
    public list_array_tt<property_t, property_list_t, RawPtr>
{
    typedef list_array_tt<property_t, property_list_t, RawPtr> Super;

 public:
    property_array_t() : Super() { }
    property_array_t(property_list_t *l) : Super(l) { }
};
// 跳转 property_list_t
struct property_list_t : entsize_list_tt<property_t, property_list_t, 0> {
};

调试如下:p/x $5.properties()

image-20220426165506500

超过属性数量的时候就出现Assertion failed

如果给类添加成员变量,会不会出现在属性列表里呢?修改,重试一遍:

image-20220426171358360

属性列表里数量没变,也就是没有成员变量_privateProperty。欲知后事如何,且听下回分解。iOS底层专栏

总结

isa指向

实例对象 -> 类对象 -> 元类 -> 根元类 -> 根元类自身。

类的继承链

对于NSObject、父类、子类,他们的类对象、元类对象都保持继承关系。

根元类的父类就是NSObject类对象。

NSObject类对象是万类之祖,没有父类。

类对象

类对象本质为objc_class结构体,有且只有一个。存储了isasuperclass(父类)、bits(属性、实例方法、协议、成员变量)、cache(方法缓存)。

class_rw_t

初步认为类的信息存储在class_rw_t结构体中,已经验证包含了属性和方法。

entsize_list_tt

entsize_list_tt 是个模板,可以实例化出method_list_t、ivar_list_t、property_list_t三种类型。

// Element:表示元素类型 List:表示容器类型 FlagMask:标记位
template <typename Element, typename List, uint32_t FlagMask, typename PointerModifier = PointerModifierNop>

.cxx_destruct

.cxx_destruct方法是在ARC模式下用于释放成员变量的。只有当前类拥有实例变量时这个方法才会出现,property生成的实例变量也算,且父类的实例变量不会导致子类拥有这个方法

大小端

大端的意思就是高位字节排放在内存的低地址端,低位字节排放在内存的高地址端。小端就相反。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

iOS底层-类的三顾茅庐(一) 的相关文章

  • 我用C语言玩对象,封装相似算法的策略模式

    概述 在策略模式 xff08 Strategy Pattern xff09 中 xff0c 一个类的行为或其算法可以在运行时更改 这种类型的设计模式属于行为型模式 在策略模式中 xff0c 我们创建表示各种策略的对象和一个行为随着策略对象改
  • 浅谈linux - 库文件制作与使用

    概述 本质上来说 xff0c 库文件就是可执行代码的二进制形式 xff0c 可以被操作系统载入内存中执行 Linux操作系统支持的函数库分为静态库和动态库 xff0c 动态库又称共享库 Linux系统有几个重要的目录存放相应的函数库 xff
  • 浅谈linux - vs code搭建运行调试环境

    概述 在Linux开发过程中 xff0c 用不惯vim的用户往往会选择vs code软件或者source insight软件编写相关的源代码 xff0c 由于vs code对linux系统的支持及界面的美观 xff0c 所以小编往往会选择使
  • 浅谈linux - samba实现linux与windows文件共享

    概述 Samba是一个能让Linux系统应用Microsoft网络通讯协议的软件 xff0c 而SMB是Server Message Block的缩写 xff0c 即为服务器消息块 xff0c SMB主要是作为Microsoft的网络通讯协
  • mysql的update、delete、和insert into时表别名用法

    mysql数据库update delete 和insert into表的时候 xff0c 如果要使用表别名 xff0c 必须按照规范写法来写 xff1a insert xff1a 简写 xff1a insert into t a a a i
  • 浅谈linux - RT Linux解决方案

    概述 RT Linux Real Time Linux 亦称作实时 Linux xff0c 是 Linux 中的一种硬实时操作系统 xff0c 它最早由美国墨西哥理工学院的 V Yodaiken 开发 产品资料提供的 Linux RT 内核
  • 浅谈linux - 线程的基本应用

    概述 线程 xff08 英语 xff1a thread xff09 是操作系统能够进行运算调度的最小单位 它被包含在进程之中 xff0c 是进程中的实际运作单位 注意 线程和进程之间的区别 1 线程是执行的基本单位 xff1b 进程是资源分
  • 浅谈linux - mutex锁应用

    概述 互斥锁是专门用于处理线程之间互斥关系的一种方式 xff0c 它有两种状态 xff1a 上锁状态 解锁状态 如果互斥锁处于上锁状态 xff0c 那么再上锁就会阻塞到这把锁解开为止 xff0c 才能上锁 解锁状态下依然可以解锁 xff0c
  • 浅谈linux - cond条件变量应用

    概述 条件变量不是一个把锁 xff0c 它实质上一个类似信号的东西 xff0c 与锁相互配合使用 xff0c 因为锁所能达到的功能就只有加锁和解锁 xff0c 并不能实现线程之间的一些关联 xff0c 于是条件变量就出现了 xff0c 与锁
  • 设计UI - Adobe xd画板及参考线

    画板 新建画板 a 使用预设画板大小或创建自定义画板 操作步骤 xff1a 打开xd软件 xff0c 点击需要建立的画板模版 xff0c 没有则选择自定义大小 b 使用画板工具创建其它画板 操作步骤 xff1a 选中画板工具 xff0c 选
  • activity的启动模式与newIntent()

    很多知识当我们用到的时候 xff0c 才发觉有多好用 今天需要完成一个功能 xff0c 创建一个悬浮窗 xff0c 点击悬浮窗上按钮 xff0c 加载到一个包含webView 的activity xff0c 为了避免activity重复创建
  • 基于DOCKER安装华为自研数据库高斯(GAUSS)

    华为数据库裸机安装比较困难 xff0c 我们可以采用docker的安装方式 xff0c 自己学习和测试使用 安装环境 1 xff09 centos 7 9 2 xff09 docker版本 opengauss 注意 xff0c 我们在安装此
  • MariaDB 中文乱码问题解决

    编辑文件 vim etc my cnf d server cnf 在 mysqld 下面添加两行 init connect 61 39 SET NAMES utf8 39 character set server 61 utf8 重启Mar
  • 【Linux】安装kali遇到的一些细节

    前言 本文省略安装教程 xff0c 如有安装教程需求 xff0c 请自行查阅 博主安装的版本为 xff1a kali 2023 1 amd 64 一 gdm3默认管理器 情景 其它系统基础配置进度完成后 xff0c 大部分安装的人 xff0
  • Windows 2016 修改密码时提示密码不符合规则

    一 现象描述 在修改服务器密码时 xff0c 遇到如下情况 xff1a 提示密码不满足密码策略要求 二 处理方法 在电脑服务器管理中找到 工具 单击 本地安全策略 进入策略管理 在本地安全策略中找到帐户安全策略 xff0c 单击进入 帐户策
  • MATLAB即将跌出TOP 20,TIOBE 4月编程语言排行榜出炉

    CSDN 编者按 一月一次的编程语言排行榜出炉 xff01 责编 张红月 出品 CSDN xff08 ID xff1a CSDNnews xff09 在TIOBE发布的4月编程语言排行榜中 xff0c 知名数学算法分析语言MATLAB即将跌
  • python3 爬虫实战案例 (抓取淘宝信息)(淘宝加了搜索必须登录的验证,此方法所到的结果都是0)

    需求 xff1a 对比足球 xff0c 篮球 xff0c 乒乓球 xff0c 羽毛球 xff0c 网球 xff0c 相关物品的销售量保存到excle中 和抓取淘宝关键字相关信息的销售量 xff0c 这和之前抓取csdn网站浏览量取不同 xf
  • android studio中Gradle 编译需要重点注意gradle,wrapper,build tools之间的版本对应关系

    Android Studio 2 3 的平台已经已经是相对稳定的发布版 xff0c 新的功能不断推出 xff0c 包括对NDK 的完美支持 它看起来有一些重大的改变也正在等待合适的孵化时机 xff0c 如 xff1a 新的 Gradle 构
  • VNC Viewer 设置屏幕分辨率-解决屏幕分辨率问题

    先介绍一款好用的连接工具介绍一个 VNC连接工具 xff1a http fwqglgj iis7 net cp vnc tscc IIs7服务器管理工具可以批量连接并管理VNC服务器 作为服务器集成管理器 xff0c 它最优秀的功能就是批量
  • LAMP环境简单搭建

    一 简介 LAMP 是Linux Apache MySQL PHP的简写 xff0c 其实就是把Apache MySQL以及PHP安装在Linux系统上 xff0c 组成一个环境来运行php的脚本语言 Apache是最常用的WEB服务软件

随机推荐

  • 转身不带走一丝云彩--我的2014

    时间或许就是这样不管你愿意不愿意都会毫不犹疑的向前 xff0c 逼你成长 2014年得到了很多也失去了很多 xff0c 我对未来还是有诸多憧憬的 谨以此文献给过去的时光 xff0c 也希望对后来人能有所帮助 改变篇 相比于2013年 xff
  • 一年装三次Arch Linux,每次都有新收获

    只要跟随优秀的教程 xff0c 装机过程so easy archlinux 双系统真机安装演示 哔哩哔哩 bilibili Archlinux系统安装 xff0c 配置 xff0c 游戏 黑客驰HackerChi 哔哩哔哩 bilibili
  • Linux系统中PS1命令详解

    本帖主要针对经常使用Linux命令行且对命令行界面美观和可阅读性有一定要求的同学 xff0c 主要讲解如何通过修改PS1命令定义命令行的显示以及提供一条可直接复用的PS1命令 原文链接 简介 xff1a PS1命令是linux系统中的一个全
  • firewalld

    一 firewalld 介绍 firewalld 防火墙 xff0c 其实就是一个隔离工具 xff1a 工作于主机或者网络的边缘 xff0c 对于进出本主机或者网络的报文根据事先定义好的网络规则做匹配检测 xff0c 对于能够被规则所匹配的
  • yum升级CURL到最新版本的方法,非常好用

    首先 xff0c 先为你的服务器获取最新匹配的源 xff1a http mirror city fan org ftp contrib yum repo 安装新版libcurl的yum源 rpm ivh http mirror city f
  • 百度笔试题2018

    题外话 首先我要吐槽一下 xff0c csdn简直是在作死啊 xff0c 复制博文底下的那个引用太恶心了 xff0c 我复制自己的博客 xff0c 还有引用 xff0c 啥玩意啊 所以我决定换地方了 xff0c 以后github xff08
  • 你见过的最全面的Python重点知识总结!

    这是一份来自于 SegmentFault 上的开发者 64 二十一 总结的 Python 重点 由于总结了太多的东西 xff0c 所以篇幅有点长 xff0c 这也是作者 34 缝缝补补 34 总结了好久的东西 xff0c 强烈建议收藏再慢慢
  • 程序员经典语录

    程序员编程语录 1 一个好的程序员是那种过单行线马路都要往两边看的人 xff08 Doug Linder xff09 2 程序有问题时不要担心 如果所有东西都没问题 xff0c 你就失业了 xff08 软件工程的Mosher定律 xff09
  • 使用fastboot命令刷机流程详解

    一 Fastboot是什么 1 1 首先介绍Recovery模式 卡刷 在系统进行定制时 xff0c 编译系统会编译出一份ZIP的压缩包 xff0c 里面是一些系统分区镜像 xff0c 提供给客户进行手动升级 恢复系统 需要提前将压缩包内置
  • 【谷歌插件】谷歌插件制作

    文章目录 谷歌浏览器插件制作教程实现步骤成功示例问题未封装的扩展程序并非来自 Chrome 网上应用商店 谷歌浏览器插件制作 教程 教程1 xff1a https blog csdn net github 35631540 article
  • 一个刚毕业大学生的四个月苦逼程序员经历

    先来一个自我介绍 大学时排名老三 就暂且叫老三吧 xff0c 毕业于河南的一个还算可以的二本院校 xff0c 专业 地球信息科学与技术 首先介绍一下我的专业 xff0c 听着名字很高大上 xff0c 其实 xff0c 我们都叫他四不像专业
  • JS中的require、import、default、export

    刚开始学的时候经常弄混总结一下 xff1a 懒人 xff1a 1 require xff08 导入 xff09 是Commonjs的规范与module exports xff08 导出 xff09 搭配使用 2 import xff08 导
  • Ubuntu安装python3

    sudo apt get install python3 安装python3 xff0c 安装完之后系统默认还是python2 xff0c 要删除python link文件 sudo rm rf usr bin python 然后建立新连接
  • ubuntu安装shutter出现E:无法修正错误

    使用Ubuntu16 04安装shutter时出现如下错误 通过换源可以解决
  • Ubuntu不能访问Windows分区

    将Windows的快速启动关闭即可解决次问题 在电脑中安装了双系统 xff0c 但有时候在Ubuntu中访问Windows分区会出现如下错误 xff1a 以前出现过这种错误 xff0c 是因为windows系统没有完全关闭 xff0c 当时
  • Ubuntu和Windows双系统时间不对的解决办法

    在使用一系统再切换到另一个系统之后 xff0c 系统时间好像是停留在上次关闭该系统的时间 在网上的解决办法通常是 xff1a sudo gedit etc default rcS xff0c 将UTC 61 yes改成UTC 61 no 但
  • Ubuntu出现依赖关系问题 - 仍未被配置问题

    安装软件包时候出现如下错误 xff1a 但这并不是依赖问题 xff0c 使用sudo apt get f install 无法解决 其实问题是因为这六个软件包没有被完全安装或卸载 在安装其他软件的时候会出现 xff1a 就是指这六个软件 使
  • 熬夜总结!最全的Pycharm常用快捷键大全!

    版权声明 xff1a 本文为博主原创文章 xff0c 遵循 CC 4 0 BY SA 版权协议 xff0c 转载请附上原文出处链接和本声明 本文链接 xff1a https blog csdn net momoda118 article d
  • iOS底层-对象里都有什么

    前言 上篇文章说了iOS中alloc方法是怎么创建对象的 xff0c 以及对象的本质是结构体 接下来继续探究对象的内存分布 xff0c 以及对象的isa是个什么样的结构体 xff0c 存储了哪些信息 对象内存分布 已知系统给对象分配内存是1
  • iOS底层-类的三顾茅庐(一)

    前言 了解完对象的底层 xff0c 知道isa指向的是类对象 那么类 xff08 Class xff09 的本质究竟是什么 xff1f 本文顺序isa的指向 xff0c 探索类的继承链 xff0c 和类对象的结构 xff0c 并且尝试获取方