「OC」源码学习——对象的底层探索

「OC」源码学习——对象的底层探索

前言

上次我们说到了源码里面的调用顺序,现在我们继续了解我们上一篇文章没有讲完的关于对象的内容函数,完整了解对象的产生对于isa赋值以及内存申请的内容

函数内容

先把_objc_rootAllocWithZone函数的内容先贴上来

static ALWAYS_INLINE id
_class_createInstance(Class cls, size_t extraBytes,int construct_flags = OBJECT_CONSTRUCT_NONE,bool cxxConstruct = true,size_t *outAllocatedSize = nil)
{ASSERT(cls->isRealized());//断言确保类已加载并完成内存布局(即 "realized" 状态)bool hasCxxCtor = cxxConstruct && cls->hasCxxCtor(); //查看父类是否存在C++的析构函数bool hasCxxDtor = cls->hasCxxDtor();//查看是否存在C++的析构函数bool fast = cls->canAllocNonpointer();//是否支持指针优化size_t size;size = cls->instanceSize(extraBytes);//计算所需要的字节数if (outAllocatedSize) *outAllocatedSize = size;//返回计算的字节数id obj = objc::malloc_instance(size, cls);//根据类对象申请对应的内存if (slowpath(!obj)) {if (construct_flags & OBJECT_CONSTRUCT_CALL_BADALLOC) {return _objc_callBadAllocHandler(cls);}return nil;}if (fast) {obj->initInstanceIsa(cls, hasCxxDtor);//如果是优化的isa指针先进入进行判断再进入initIsa} else {// Use raw pointer isa on the assumption that they might be// doing something weird with the zone or RR.obj->initIsa(cls);}if (fastpath(!hasCxxCtor)) {return obj;//没有析构函数就可以直接返回了}construct_flags |= OBJECT_CONSTRUCT_FREE_ONFAILURE;//添加一个flagreturn object_cxxConstructFromClass(obj, cls, construct_flags);
}

我们来看看instanceSize这个函数究竟实现了什么,按照instanceSize->fastInstanceSize->align16,依次步入,我们可以看到一下内容

static inline size_t align16(size_t x) {return (x + size_t(15)) & ~size_t(15);
}

这个程序就是一个16字节对齐的算法,先抛开这个算法,若是由我们来设计这个算法我们的思路是什么呢?最简单的就是

(x + 15) / 16 * 16

当然很快我们就意识到了,使用乘法和除法运算的效率和位运算相比实在太低了,正好16是2的4次方,位运算一样的解决啊

(x + 15) >> 4 << 4

OK现在设计完了我们在来看看苹果设计的代码

(x + 15) & ~15

~有按位取反的意思通过按位与操作,将低 4 位(二进制 1111)清零,得到最近的 16 的倍数。用size(15)是为了兼容32位和64机型,因为size_t 是无符号整数类型,其位数由平台决定(32 位系统为 4 字节,64 位系统为 8 字节)。当 xsize_t 类型时,size_t(15) 确保运算中的右操作数(15)与左操作数(x类型一致,避免隐式转换带来的风险

内存内容探究

GGObject *obj = [GGObject alloc];
NSLog(@"%lu", sizeof(obj));
NSLog(@"%lu", class_getInstanceSize(obj.class));
NSLog(@"%lu", malloc_size((__bridge const void*)(obj)));

根据这个内容我们可以看到自定义类对应的大小

image-20250424205643745

当我们的类被编译了之后,底层会类编译成 isa + 成员变量,所以在给类的实例分配内存的话这个内存块存储的就是 isa + 成员变量的值

内存对齐

学过C语言我们都知道,结构体是有内存对齐这个概念的,当然不同的排列所实现的内存对齐是不同的,例如

struct Mystruct1{char a;     //1字节double b;   //8字节int c;      //4字节short d;    //2字节
}Mystruct1;struct Mystruct2{double b;   //8字节int c;      //4字节short d;    //2字节char a;     //1字节
}Mystruct2;//计算 结构体占用的内存大小
NSLog(@"%lu-%lu",sizeof(Mystruct1),sizeof(Mystruct2));// 打印:24 - 16

结构体安排的顺序会关系到结构体内存的大小,我们知道OC之中的对象其实就是由结构体构成的,那我们来探究一下不同的属性对对象的内存是否有影响呢

内存重排

对象的本质 = isa + 成员变量的值。

#import <Foundation/Foundation.h>
@interface MyPerson : NSObject
// isa 8字节
@property (nonatomic, copy) NSString *name; // 8字节
@property (nonatomic, copy) NSString *hobby; // 8字节
@property (nonatomic, assign) int age; // 4字节
@property (nonatomic, assign) double height; // 8字节
@property (nonatomic, assign) short number; // 2字节
@end

一共38字节,16字节内存对齐得到总的字节数为48字节,在探究内存问题之前,先学几个lldb命令

lldb指令:p   输出10进制p/x 输出16进制p/0 输出8进制p/t 输出2进制p/f 输出浮点数x   输出地址x/4gx  输出4个字节地址x/6gx  输出6个字节地址

现在我们用lldb进行调试

 MyPerson *p = [GGObject alloc];p.name = @"bb";p.hobby = @"吃吃睡睡喝喝";p.height = 1.80;p.age = 26;p.number = 123;

其实尝试着将类之中的属性内容,打印出来可以看到,无论属性怎么排列,这个MyPerson类age和number这两个属性永远会排在一起,这个就是编译器的内存优化,对属性进行重排

image-20250425194336343

我们看到在类的内部,我们的属性会通过重新排列获得最小内存,接下来我们看看能能不能重排父类的属性和子类属性吧

image-20250425200345352

还是可以看到子类属性并不会跟父类属性合并

@interface JCTestObject : NSObject
{@publicint count;// 若count在此处,为JCTestSubObject实例实际需要40字节,系统为实例分配48字节NSObject *obj1;NSObject *obj2;
//    int count; // 若count在此处,为JCTestSubObject实例实际需要32字节,系统为实例分配32字节}
@end
@interface JCTestSubObject : JCTestObject
{@publicint count2;
}

isa走向

之前学习过关于isa指针的部分内容,

img

结论:

类的实例isa --> 类对象;
类对象isa --> 元类对象;
元类对象isa --> 根元类对象;
根元类对象isa --> 根元类对象自己。

objc_class 与 `objc_object

objc_object 是所有 Objective-C 对象的底层结构体,其定义如下

struct objc_object {isa_t isa;  // 指向类对象或元类的指针
};

objc_class 是类的底层结构体,类对象(如 [LGPerson class])和元类(metaclass)均为此类型。其继承自 objc_object,定义如下:

struct objc_class : objc_object {Class superclass;          // 父类指针cache_t cache;             // 方法缓存(哈希表)class_data_bits_t bits;    // 指向类信息(如方法列表、属性等)
};

1

img

class_getClassMethodclass_getInstanceMethod

先定义模版类

@interface JCObject : NSObject
{int _sum;
}
@property (nonatomic, copy) NSString *name;
@property (nonatomic, copy) NSString *hobby;
@property (nonatomic, assign) int age;
@property (nonatomic, assign) double height;
@property (nonatomic, assign) short number;
- (void)speak;
- (void)sayHello;
+ (void)walk;
@end@implementation JCObject- (void)speak {NSLog(@"%s", __func__);
}
+ (void)walk {NSLog(@"%s", __func__);
}-(void) sayHello {NSLog(@"%s", __func__);
}

在完成两个函数

void jcInstanceMethod_classToMetaclass(Class pClass){const char *className = class_getName(pClass);Class metaClass = objc_getMetaClass(className);Method method1 = class_getInstanceMethod(pClass, @selector(speak));Method method2 = class_getInstanceMethod(metaClass, @selector(speak));Method method3 = class_getInstanceMethod(pClass, @selector(walk));Method method4 = class_getInstanceMethod(metaClass, @selector(walk));NSLog(@"%s - %p-%p-%p-%p",__func__,method1,method2,method3,method4);
}void jcClassMethod_classToMetaclass(Class pClass){const char *className = class_getName(pClass);Class metaClass = objc_getMetaClass(className);Method method1 = class_getClassMethod(pClass, @selector(speak));Method method2 = class_getClassMethod(metaClass, @selector(speak));Method method3 = class_getClassMethod(pClass, @selector(walk));Method method4 = class_getClassMethod(metaClass, @selector(walk));NSLog(@"%s-%p-%p-%p-%p",__func__,method1,method2,method3,method4);
}//main函数
JCObject *person = [JCObject alloc];
Class pClass = object_getClass(person);
jcObjc_copyMethodList(pClass);
jcInstanceMethod_classToMetaclass(pClass);
jcClassMethod_classToMetaclass(pClass);

得到的结果

image-20250502230010829

jcInstanceMethod_classToMetaclass

要了解jcInstanceMethod_classToMetaclass之中的内容就要从理解class_getInstanceMethod开始,顾名思义class_getInstanceMethod这个方法,主要是用于获取实例方法,如果在传入的类或者类的父类中没有找到指定的实例方法,则返回NULL。从实例方法存储在类中,类方法存储在元类中可以得知,JCObject的方法列表打印结果只有sayHello方法

jcClassMethod_classToMetaclass

需要先了解class_getClassMethod这个方法,看该方法的源码实现,可以得出class_getClassMethod的实现是获取类的类方法,其本质就是获取元类的实例方法,最终还是会走到class_getInstanceMethod

//获取类方法
Method class_getClassMethod(Class cls, SEL sel)
{if (!cls  ||  !sel) return nil;return class_getInstanceMethod(cls->getMeta(), sel);
}⬇️
//获取元类
//在getMeta源码中,如果判断出cls是元类,那么就不会再继续往下递归查找,会直接返回this,其目的是为了防止元类的无限递归查找
Class getMeta() {if (isMetaClass()) return (Class)this;else return this->ISA();
}

img

所以我们再来看看结果

对于speak方法存在于类对象之中,而walk方法存在于元类对象之中,所以使用class_getInstanceMethod可以在类对象之中找到speak方法,而在元类对象之中找到walk的类方法

而对于class_getClassMethod来说如果不是元类会先找到元类,然后再在元类之中查找类方法

iskindOfClass & isMemberOfClass

  • iskindOfClass & isMemberOfClass 类方法调用
//-----使用 iskindOfClass & isMemberOfClass 类方法
BOOL re1 = [(id)[NSObject class] isKindOfClass:[NSObject class]];       //
BOOL re2 = [(id)[NSObject class] isMemberOfClass:[NSObject class]];     //
BOOL re3 = [(id)[LGPerson class] isKindOfClass:[LGPerson class]];       //
BOOL re4 = [(id)[LGPerson class] isMemberOfClass:[LGPerson class]];     //
NSLog(@" re1 :%hhd\n re2 :%hhd\n re3 :%hhd\n re4 :%hhd\n",re1,re2,re3,re4);
  • iskindOfClass & isMemberOfClass 实例方法调用
//------iskindOfClass & isMemberOfClass 实例方法
BOOL re5 = [(id)[NSObject alloc] isKindOfClass:[NSObject class]];       //
BOOL re6 = [(id)[NSObject alloc] isMemberOfClass:[NSObject class]];     //
BOOL re7 = [(id)[LGPerson alloc] isKindOfClass:[LGPerson class]];       //
BOOL re8 = [(id)[LGPerson alloc] isMemberOfClass:[LGPerson class]];     //
NSLog(@" re5 :%hhd\n re6 :%hhd\n re7 :%hhd\n re8 :%hhd\n",re5,re6,re7,re8);

img

  • isKindOfClass
    检查对象是否属于指定类或其继承链中的任意父类。例如,若对象是 Person 类的子类实例(如 Student),则 [student isKindOfClass:[Person class]] 返回 YES
//--isKindOfClass---类方法、对象方法
//+ isKindOfClass:第一次比较是 获取类的元类 与 传入类对比,再次之后的对比是获取上次结果的父类 与 传入 类进行对比
+ (BOOL)isKindOfClass:(Class)cls {// 获取类的元类 vs 传入类// 根元类 vs 传入类// 根类 vs 传入类// 举例:LGPerson vs 元类 (根元类) (NSObject)for (Class tcls = self->ISA(); tcls; tcls = tcls->superclass) {if (tcls == cls) return YES;}return NO;
}//- isKindOfClass:第一次是获取对象类 与 传入类对比,如果不相等,后续对比是继续获取上次 类的父类 与传入类进行对比
- (BOOL)isKindOfClass:(Class)cls {
/*
获取对象的类 vs 传入的类 
父类 vs 传入的类
根类 vs 传入的类
nil vs 传入的类
*/for (Class tcls = [self class]; tcls; tcls = tcls->superclass) {if (tcls == cls) return YES;}return NO;
}
  • isMemberOfClass
    严格检查对象是否直接属于指定类,不包含继承链。例如,Student 实例调用 isMemberOfClass:[Person class] 会返回 NO,因为其直接类是 Student 而非 Person
//-----类方法
//+ isMemberOfClass : 获取类的元类,与 传入类对比
+ (BOOL)isMemberOfClass:(Class)cls {return self->ISA() == cls;
}
//-----实例方法
//- isMemberOfClass : 获取对象的类,与 传入类对比
- (BOOL)isMemberOfClass:(Class)cls {return [self class] == cls;
}

参考文章

iOS-底层原理 09:类 & isa 经典面试题分析

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

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

相关文章

【C++指南】STL list容器完全解读(一):从入门到掌握基础操作

. &#x1f493; 博客主页&#xff1a;倔强的石头的CSDN主页 &#x1f4dd;Gitee主页&#xff1a;倔强的石头的gitee主页 ⏩ 文章专栏&#xff1a;《C指南》 期待您的关注 文章目录 一、初识list容器1.1 什么是list&#xff1f;1.2 核心特性1.3 典型应用场景 二、核心成员函数…

labelimg快捷键

一、核心标注快捷键 ‌W‌&#xff1a;调出标注十字架&#xff0c;开始绘制矩形框&#xff08;最常用功能&#xff09;‌A/D‌&#xff1a;切换上一张(A)或下一张(D)图片&#xff0c;实现快速导航‌Del‌&#xff1a;删除当前选中的标注框 二、文件操作快捷键 ‌CtrlS‌&…

linux-文件操作

在 Linux 系统中&#xff0c;文件操作与管理是日常使用和系统管理的重要组成部分。下面将详细介绍文件的复制、移动、链接创建&#xff0c;以及文件查找、文本处理、排序、权限管理等相关知识。 一、文件的复制 在 Linux 里&#xff0c;cp 命令可用于复制文件或目录&#xff…

C++ 复习

VS 修改 C 语言标准 右键项目-属性 输入输出 //引用头文件&#xff0c;用<>包裹起来的一般是系统提供的写好的代码 编译器会在专门的系统路径中去进行查找 #include <iostream> //自己写的代码文件一般都用""包裹起来 编译器会在当前文件所在的目录中査…

openGauss新特性 | HTAP新特性介绍

一、行列融合功能简介 HTAP 行列融合特性在单机、主备场景下&#xff0c;通过节点的行列双格式内存模式&#xff0c;实现openGauss HTAP一体化数据库架构。 通过高效的行列转换技术方案&#xff0c;节点读取磁盘行存数据&#xff0c;生成列存储单元&#xff08;Column Unit&am…

双目测量中的将视差图重投影成三维坐标图

双目测距主要步骤如下&#xff1a; 左右两张图片 → 匹配 → 得到视差图 disp&#xff1b; 使用 cv2.reprojectImageTo3D(disp, Q) 将视差图 重投影 成三维坐标图 → 得到 points_3d 什么是 points_3d&#xff1f; points_3d cv2.reprojectImageTo3D(disp, Q)points_3d.shap…

《深度剖析:SOAP与REST,API集成的两极选择》

API作为不同系统之间交互的桥梁&#xff0c;其设计与实现的优劣直接影响着整个软件生态的运转效率。而在API的设计领域&#xff0c;SOAP和REST犹如两座巍峨的山峰&#xff0c;各自代表着截然不同的设计理念与应用方向&#xff0c;成为开发者在构建API时必须慎重权衡的关键选项。…

非对称加密算法(RSA、ECC、SM2)——密码学基础

对称加密算法&#xff08;AES、ChaCha20和SM4&#xff09;Python实现——密码学基础(Python出现No module named “Crypto” 解决方案) 这篇的续篇&#xff0c;因此实践部分少些&#xff1b; 文章目录 一、非对称加密算法基础二、RSA算法2.1 RSA原理与数学基础2.2 RSA密钥长度…

Pillow 玩图术:轻松获取图片尺寸和颜色模式

前言 在这个“图像为王”的时代,谁还敢说自己没被一张图折磨过?一张图片不讲武德,说崩就崩,说卡就卡,仿佛像素里藏着程序员的眼泪。不管你是网页设计师、AI炼丹师,还是只是想把猫片修得像艺术品,图片的尺寸和颜色模式都是你必须掌握的第一手情报。如果你不知道它有多宽…

下载core5compat 模块时,被禁止,显示 - servese replied: Forbbidden. -->换镜像源

怎么解决&#xff1f; --->换镜像源 方法 1&#xff1a;使用命令行参数指定镜像源 在运行 Qt 安装器时&#xff0c;通过 --mirror 参数指定镜像源&#xff1a; # Windows qt-unified-windows-x64-online.exe --mirror https://mirrors.ustc.edu.cn/qtproject# Linux/macO…

WPF中Behaviors

行为的好处 可以把复杂的界面逻辑抽象出去&#xff0c;让xaml的界面设计更简单&#xff0c;更清爽 1.安装包 Microsoft.Xaml.Behaviors.Wpf2.简单实现拖动效果 <Border Width"100"Height"100"Background"Red"><i:Interaction.Behav…

GitHub 趋势日报 (2025年05月03日)

本日报由 TrendForge 系统生成 https://trendforge.devlive.org/ &#x1f4c8; 今日整体趋势 Top 10 排名项目名称项目描述今日获星总星数语言1hacksider/Deep-Live-Camreal time face swap and one-click video deepfake with only a single image⭐ 1582⭐ 59337Python2aip…

Oracle OCP认证考试考点详解083系列08

题记&#xff1a; 本系列主要讲解Oracle OCP认证考试考点&#xff08;题目&#xff09;&#xff0c;适用于19C/21C,跟着学OCP考试必过。 36. 第36题&#xff1a; 题目 解析及答案&#xff1a; 关于数据库闪回&#xff08;FLASHBACK DATABASE&#xff09;功能&#xff0c;以下…

优化01-统计信息

Oracle 的统计信息是数据库优化器生成高效执行计划的核心依据。它记录了数据库对象&#xff08;如表、索引、列等&#xff09;的元数据信息&#xff0c;帮助优化器评估查询成本并选择最优执行路径。以下是关于 Oracle 统计信息的详细介绍&#xff1a; 一、统计信息的分类 表统…

动态规划-面试题08.01三步问题-力扣(LeetCode)

一、题目解析 此题可以类比第N个泰波那契数 二、算法解析 1、状态表示 根据上面的分析和题目要求&#xff0c;dp[i]表示&#xff1a;到达i位置&#xff0c;一共有多少种方法 2、状态转移方程 以i位置的状态&#xff0c;以最近一步划分问题 dp[i] 从i-1->i dp[i-1] 从…

kotlin中枚举带参数和不带参数的区别

一 ✅ 代码对比总结 第一段&#xff08;带参数 工具方法&#xff09; enum class SeatPosition(val position: Int) {DRIVER_LEFT(0),DRIVER_RIGHT(1),SECOND_LEFT(2),SECOND_RIGHT(3);companion object {fun fromPosition(position: Int): SeatPosition? {return SeatPosi…

Java使用JDBC操作数据库

1.创建一个数据库一会用来连接 2.使用idea新建一个Java项目 3.在pom文件中加上相关依赖&#xff0c;并配置Maven路径 <dependencies><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>…

重名导致does not name a type

今天在Ubuntu24.04上编成时&#xff0c;makefile编译报错: falsecolor.h:48:9: error: ‘FalseColor’ does not name a type48 | FalseColor* content ;| ^~~~~~~~~~falsecolor.h的部分代码如下: class FalseColor {public:FalseColor(int w, int h){width …

Vue3 后台管理系统模板

Vue3 后台管理系统模板 gie仓库地址 一个基于 Vue3 TypeScript Element Plus 的后台管理系统模板&#xff0c;集成了动态路由和权限管理功能。 技术栈 Vue 3.2TypeScript 4.5Vue Router 4Vuex 4Element Plus 2.9AxiosLess 功能特性 &#x1f680; 基于 Vue3 最新技术栈开…

林业数智化转型初步设计方案

最近应林业方面的朋友要求,帮助其设计了林业方面的数字化智能化转型的方案设计,编写了如下内容,供大家参考,林业方面主要有三大方向,即林业生态、生物灾害和疫源疫病,目前已经建成了一些信息化系统,但在数字化智能化方面偏弱,就想着如何借助人工智能、物联网、大数据和…