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

「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.xdnf.cn/news/293185.html

相关文章:

  • 混搭文化数字社会学家解读,创新理解AI社会学网络社会学与数字人类学最新研究进展社会结构社会分层数字文化数字经济
  • 网络编程套接字(一)
  • PriorityQueue
  • 使用 Semantic Kernel 快速对接国产大模型实战指南(DeepSeek/Qwen/GLM)
  • Web前端开发:Grid 布局(网格布局)
  • ts学习(1)
  • 2024年408真题及答案
  • C++ 外观模式详解
  • php8 枚举使用教程
  • 稀疏性预测算法初步
  • 健康养生:从微小改变开始
  • 【YOLO11改进】改进Conv、颈部网络STFEN、以及引入PIOU用于小目标检测!
  • 基于Vue3开发:打造高性能个人博客与在线投票平台
  • 【MATLAB例程】基于RSSI原理的Wi-Fi定位程序,N个锚点(数量可自适应)、三维空间,轨迹使用UKF进行滤波,附代码下载链接
  • 反射-探索
  • CASS 3D使用等高线修改插件导致修后等高线高程变化的问题
  • 当前人工智能领域的主流高级技术及其核心方向
  • 10.施工测量
  • 引领变革的“Vibe Coding”:AI辅助编程的崛起与挑战
  • 某信服EDR3.5.30.ISO安装测试(一)
  • printf的终极调试大法
  • 分析 Docker 磁盘占用
  • FTP/TFTP/SSH/Telnet
  • FastMCP - 快速、Pythonic风格的构建MCP server 和 client
  • [人机交互]交互设计
  • Qwen3的“混合推理”是如何实现的
  • Kotlin-空值和空类型
  • 【AI提示词】SCAMPER法专家
  • 【最新Python包管理工具UV的介绍和安装】
  • SIFT算法详细原理与应用