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

【iOS】源码阅读(四)——isa与类关联的原理

文章目录

  • 前言
  • OC对象本质探索
    • clang
    • 探索对象本质
    • objc_setProperty源码探索
  • cls与类的关联原理
    • 为什么说bits与cls为互斥关系
    • isa的类型isa_t
    • 原理探索
    • isa与类的关联
  • 总结

前言

  本篇文章主要是笔者在学习和理解类与isa的关联关系时所写的笔记。

OC对象本质探索

  在学习和理解类与isa的关联关系之前,我们先来探寻一下OC对象的本质。探寻其本质,我们首先要了解一个编译器:clang。

clang

  Clang 是一个开源的 C/C++/Objective-C/Objective-C++ 编译器,属于 LLVM(Low Level Virtual Machine) 项目的一部分。它被设计为 GCC(GNU Compiler Collection) 的替代品,提供更快的编译速度、更低的内存占用、更清晰的错误提示以及更好的兼容性。
  这个编译器是xcode自带的,主要是用于底层编译,将一些文件``输出成c++文件,例如main.m 输出成main.cpp,其目的是为了更好的观察底层的一些结构 及 实现的逻辑,方便理解底层原理。

探索对象本质

  在main文件中自定义一个GGObject类,并声明一个name属性。
在这里插入图片描述
  通过终端,利用clang将main.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 

在这里插入图片描述
  然后我们打开编译好的main.cpp文件,找到关于GGObject的定义,我们能发现GGObject在底层会被编译成struct结构体。

GGObject的底层编译:
在这里插入图片描述

struct GGObject_IMPL {struct NSObject_IMPL NSObject_IVARS;//定义一个自定义类 GGObject 的底层实现(通常对应 .m 文件中的类扩展);_IMPL 后缀表明这是类的私有实现结构体,用于组织实例变量//嵌入 NSObject 的实例变量结构体(通常是私有定义,如 isa 指针);NSObject_IVARS 表示这是 NSObject 的实例变量集合。NSString *_name;
};

  从上述代码中我们能看出来,GGObject_IMPL中的第一个属性是NSObject_IMPL结构体,从名称上来看,这貌似是继承自NSObject的。我们找到关于NSObject的定义及底层编译来进一步探索。

NSObject的定义:
在这里插入图片描述

@interface NSObject <NSObject> {  // 声明 NSObject 的类接口
#pragma clang diagnostic push    // 保存当前编译器警告状态
#pragma clang diagnostic ignored "-Wobjc-interface-ivars"  // 禁用 "接口中声明实例变量" 警告Class isa  OBJC_ISA_AVAILABILITY;  // 声明一个名为 isa 的实例变量
#pragma clang diagnostic pop     // 恢复之前的警告状态
}

NSObject的底层编译:
在这里插入图片描述
  由上述,我们可以知道,GGObject_IMPL中的第一个属性其实就是 isa,是继承自NSObject,属于伪继承,伪继承的方式是直接将NSObject结构体定义为LGPerson中的第一个属性,意味着GGObject 拥有 NSObject中的所有成员变量。GGObject中的第一个属性 NSObject_IVARS 等效于 NSObject中的 isa。

GGObject类的完整定义如下:
在这里插入图片描述
  通过上述探索,我们了解了OC对象的本质,同时我们产生了一个疑问:为什么NSObject_IMPL中的isa的类型为class?
  在之前探索alloc实现过程时,提及过alloc方法的核心之一的initInstanceIsa方法,通过查看这个方法的源码实现,我们发现,在初始化isa指针时,是通过isa_t类型初始化的,而在NSObject定义中isa的类型是Class,其根本原因是由于isa 对外反馈的是类信息,为了让开发人员更加清晰明确,需要在isa返回时做了一个类型强制转换。这里在源码中代码如下:
在这里插入图片描述

  另外,关于class类型的本质我们可以在objc.h文件中找到对应的定义:

typedef struct objc_class *Class;

这里我们可以知道,它是指向objc_class结构体的指针,存储类的元数据(即方法列表、属性列表、协议等)。

而当isa是class类型时,它直接指向对象的类:

Class cls = object_getClass(object);

总结
由以上我们可以得出:

  1. OC对象的本质就是结构体。
  2. GGObject中的isa是继承于NSObject中的isa。

objc_setProperty源码探索

  在前面探索GGObject源码的时候,我们还发现了我们声明的name属性对应的set和get方法在底层的代码实现,其中set方法的实现依赖于runtime中的objc_setProperty:
在这里插入图片描述
我们通过xcode自带的搜索功能可以进一步对objc_setProperty源码进行探索:
在这里插入图片描述
然后我们通过command+点击,可以进入reallySetProperty的实现代码:

static inline void reallySetProperty(id self, //对象实例SEL _cmd, //setter方法名(未直接使用)id newValue, //新属性值ptrdiff_t offset, //属性在对象内存中的偏移量bool atomic, //是否为原子操作(决定是否加锁)bool copy, //是否用copy修饰bool mutableCopy //是否用mutableCopy修饰//copy和mutableCopy决定是否需要对newValue执行拷贝
)
{if (offset == 0) { //offset == 0 表示操作的是对象的 isa 指针(isa 是对象的第一个成员,偏移量为 0)object_setClass(self, newValue); //调用 object_setClass 动态修改对象的类(返回值为对象原来的类)//obj:要修改类的对象(实例)//cls:目标类(必须是有效的 objc_class)//这里将对象 obj 的 isa 指针从原来的类改为 cls;不修改对象的内存布局,仅改变类信息,不影响实例变量(ivar)的存储return;}id oldValue;//计算属性内存地址id *slot = (id*) ((char*)self + offset);//slot:指向属性在对象内存中的地址//(char*)self:将对象指针转为字节指针(便于偏移计算)//+ offset:跳转到属性的实际存储位置//处理copy/mutableCopy修饰if (copy) {newValue = [newValue copyWithZone:nil];} else if (mutableCopy) {newValue = [newValue mutableCopyWithZone:nil];} else {if (*slot == newValue) return;newValue = objc_retain(newValue);}//非原子性处理(不加锁,直接赋值,这样会更高效)if (!atomic) {oldValue = *slot; //保存旧值*slot = newValue; //直接赋值} else {//原子性处理(通过自旋锁,即spinlock_t保证线程安全)spinlock_t& slotlock = PropertyLocks[slot]; //PropertyLocks 是一个全局锁表,每个属性地址对应一个锁slotlock.lock(); //加锁oldValue = *slot;*slot = newValue; //赋值slotlock.unlock(); //解锁}//释放酒值(调用 objc_release 减少旧值的引用计数)objc_release(oldValue);
}

所以通过上述代码,我们可以总结出其调用链:

  1. 编译器生成 setName: 方法,调用 objc_setProperty。
  2. objc_setProperty 内部调用 reallySetProperty。
  3. reallySetProperty 根据修饰符(copy/atomic)处理赋值。

总结

  • objc_setProperty方法是 Objective-C 运行时提供的 统一属性设置入口,本质上是一个 适配层(Adapter),负责桥接编译器生成的属性设值方法(Setter)与底层内存管理操作。(即适用于 关联上层的set方法 以及 底层的set方法,其本质就是一个接口)
  • 这么设计的原因是,为了进行接口统一化和隔离变化。
  • 基于上述原因,苹果采用了适配器设计模式(即将底层接口适配为客户端需要的接口),对外提供一个接口,供上层的set方法使用,对内调用底层的set方法,使其相互不受影响,即无论上层怎么变,下层都是不变的,或者下层的变化也无法影响上层,主要是达到上下层接口隔离的目的

(1) 接口统一化
问题:编译器为每个 @property 生成独立的 Setter 方法,若直接调用底层内存操作(如 reallySetProperty),会导致:
二进制体积膨胀(每个 Setter 都包含重复的内存管理代码)。
难以集中优化(如原子性、拷贝逻辑的处理)。
解决方案:通过 objc_setProperty 收敛所有属性设值逻辑,成为唯一入口。
(2) 隔离变化
上层(编译器生成的 Setter):只需关注如何传递参数(如属性偏移量、修饰符标志)。
下层(内存管理):只需处理标准化后的参数(如 atomic/copy 标志),无需关心调用来源。

请添加图片描述
上述代码中,我们看到关于分别用copy和mutableCopy修饰的底层逻辑,笔者在这里再根据之前所学进行一下回顾:

copy 修饰符

当属性声明为 copy 时,reallySetProperty 会调用 [newValue copyWithZone:nil]。生成的是 不可变副本(即使 newValue 是可变对象)

eg:

@property (nonatomic, copy) NSString *name;

如果传入 NSMutableString,会被转为 NSString:

NSMutableString *mutableName = [NSMutableString stringWithString:@"Alice"];
obj.name = mutableName; // 实际存储的是不可变的 @"Alice" 副本
[mutableName appendString:@"Bob"]; // 不影响 obj.name(仍是 @"Alice")

当我们需要确保属性值不被外部修改时(如 NSString、NSArray、NSDictionary 等),可以使用copy来防止传入可变对象后,外部修改导致属性值意外变化。

mutableCopy 修饰符
当属性声明为 mutableCopy 时,reallySetProperty 会调用 [newValue mutableCopyWithZone:nil]。生成的是可变副本(即使newValue是不可变对象)

eg:

@property (nonatomic, mutableCopy) NSMutableArray *items;

如果传入 NSArray,会被转为 NSMutableArray:

NSArray *immutableItems = @[@"A", @"B"];
obj.items = immutableItems; // 存储的是可变的副本
[obj.items addObject:@"C"]; // 允许修改

当我们需要属性值可被内部修改时(较少使用,通常直接声明为 NSMutableArray 类型)可以使用mutableCopy。

默认行为(无 copy/mutableCopy)

若未指定 copy 或 mutableCopy,则直接 保留(retain) 新值:

newValue = objc_retain(newValue);

新旧值相同时优化:如果 *slot == newValue,直接返回,避免无谓操作。

请添加图片描述
在刚刚 reallySetProperty 的源码中:

if (copy) {newValue = [newValue copyWithZone:nil]; // 生成不可变副本
} else if (mutableCopy) {newValue = [newValue mutableCopyWithZone:nil]; // 生成可变副本
} else {newValue = objc_retain(newValue); // 默认 retain
}

其中的copyWithZone: 和 mutableCopyWithZone:是 NSCopying 和 NSMutableCopying 协议的方法,由对象类实现。
eg:NSString的copy返回自身(不可变),mutableCopy返回NSMutableString。

copy和mutableCopy的内存管理规则

请添加图片描述

实际应用

  1. 优先使用copy修饰不可变对象:
@property (nonatomic, copy) NSString *text;
@property (nonatomic, copy) NSArray *items;

避免传入 NSMutableString/NSMutableArray 后,原属性值被外部修改。

  1. 谨慎使用mutableCopy修饰不可变对象:
    除非明确需要可变副本,否则直接用 NSMutableArray 等类型。
  2. 自定义类支持copy:
    实现NSCopying协议:
@interface Person : NSObject <NSCopying>
@end@implementation Person
- (id)copyWithZone:(NSZone *)zone {Person *copy = [[Person alloc] init];copy.name = self.name; // 深拷贝或浅拷贝按需实现return copy;
}
@end

了解到这里经常会涉及到一个经典问题:为什么NSString要用copy修饰?
这是为了防止可变字符串篡改:

NSMutableString *mutableName = [NSMutableString stringWithString:@"Alice"];
self.name = mutableName; // 若用 strong,mutableName 修改会影响 self.name
[mutableName appendString:@"Bob"];
// 若 name 是 copy,self.name 仍是 @"Alice";若 strong,会变成 @"AliceBob"

cls与类的关联原理

在这里我们有两个疑问:为什么isa的类型是isa_t ?initInstanceIsa是如何将cls与isa关联的?
首先,我们可以在objc-private.h文件中找到关于isa的类型isa_t的定义源码:

#include "isa.h"union isa_t { //联合体//构造函数isa_t() { }isa_t(uintptr_t value) : bits(value) { }uintptr_t bits;//存储完整的 isa 值(64 位整数)//私有成员:类指针(通过方法访问)
private:// Accessing the class requires custom ptrauth operations, so// force clients to go through setClass/getClass by making this// private.Class cls;//将 cls 设为私有进行直接访问限制,强制外部代码通过 setClass/getClass 方法访问。//这里的bits与cls是互斥关系public:
#if defined(ISA_BITFIELD)//条件编译:位域结构(由isa.h定义)struct {ISA_BITFIELD;  // defined in isa.h};//內联引用计数相关方法(仅当ISA_HAS_INLINE_RC启用时)
#if ISA_HAS_INLINE_RCbool isDeallocating() const {return extra_rc == 0 && has_sidetable_rc == 0;}void setDeallocating() {extra_rc = 0;has_sidetable_rc = 0;}
#endif // ISA_HAS_INLINE_RC#endif//公共方法:安全访问类指针void setClass(Class cls, objc_object *obj);Class getClass(bool authenticated) const;Class getDecodedClass(bool authenti

为什么说bits与cls为互斥关系

这里就涉及到Union的内存共享特性:
​​内存复用​​:union 的所有成员共享同一块内存,大小由最大的成员决定。
​​互斥性​​:同一时间只能使用一个成员。
eg:我们现在对Union的内存复用和互斥性进行代码实感

union Example {int a;float b;
};
Example u;
u.a = 42;  // 此时 u.b 的值是未定义的
u.b = 3.14; // 此时 u.a 的值被覆盖
NSLog(@"u.a = %d\nu.b = %f", u.a, u.b);

这里的输出如下:

在这里插入图片描述
为什么会出现这种情况呢?我们来逐行解析一下以上代码:

  1. 首先我们给u.a赋值为42,这是内存中存储的是整数值42的二进制表示。
  2. 然后给u.b赋值为3.14,这时同一块内存被浮点数3.14的二进制表示覆盖。

u.a显示为1078523331,这是因为当我们给u.b赋值为3.14时,这块内存被重新解释为整数,其整数值就是1078523331,而为什么这里3.14对应的整数值是1078523331,这里需要大概了解一下IEEE 754浮点数表示法:
我们就以3.14为例:
符号位:0(正数)
指数:128(二进制为10000000,实际指数为128-127=1)
尾数:100100011110101110000101(3.14的二进制,去掉前导1之后)

将这些二进制位组合起来得到的32位表示是:
0 10000000 100100011110101110000101
将其解释为无符号整数,得到的就是1078523331。

小结
联合体中所有成员共享相同的内存地址,所以当你通过一个成员修改内存内容时,其他所有成员的值都会被改变,因为它们查看的是同一块内存。

isa的类型isa_t

  通过上面isa的类型isa_t的定义源码,我们可以得知,其是通过联合体定义的。为什么要通过联合体而非结构体进行定义呢?
这主要是为了内存优化​​,使用联合体定义来共享内存空间​​。

​​联合体特性​​:所有成员共享同一块内存,大小由最大成员决定。
​​isa_t 的需求​​:需要同时存储 ​​类指针​​(Class)和 ​​位域​​(如引用计数、标记位等),但无需同时使用它们。
​​内存节省​​:若用结构体(struct),每个成员独立占用内存;而联合体复用内存,避免冗余。

  另外,我们从isa_t的定义中还可以知道,这里提供了两个成员,即 cls 和 bits ,由于这两者是互斥的,这就意味着,当初始化 isa 指针时,有两种初始化方式:

  • 通过 cls 初始化(设置类指针):

调用 setClass 方法,修改 bits 中的类指针字段(高位),保留其他位域(如引用计数)的原始值。仅修改 bits 中类指针对应的位,其他位域(如 extra_rc)​​未被初始化​​,保持未定义的垃圾值。

初始化结果:
cls(通过 getClass 方法解密后)是有效的类指针。
bits 的其他字段(如引用计数)​​无默认值​​(可能是随机值)。
请添加图片描述

  • 通过 bits 初始化(直接操作内存位域):

直接赋值 bits,覆盖所有字段(包括类指针和位域)。如果赋值的 bits 包含有效的类指针(加密后)和位域,则 cls 会同步更新;如果 bits 未正确设置类指针,则 cls 可能为无效值。
​​
初始化结果​​:
bits 的所有字段(包括类指针)被显式设置。
cls 的值由 bits 中对应的位决定:如果 bits 包含有效类指针,则 cls 有效;否则为无效值(非默认值)。
请添加图片描述

所以,我们通过cls初始化,bits无默认值;通过bits进行初始化,cls有默认值。

上面提到两种初始化方法时,一直在提到位域,这到底是什么,在isa_t定义源码中提供了一个结构体定义的位域内容:

public:
#if defined(ISA_BITFIELD)//条件编译:位域结构(由isa.h定义)struct {ISA_BITFIELD;  // defined in isa.h};

其中,这里的结构体成员ISA_BITFIELD是一个宏定义,我们具体来看一下这里面都有什么内容:

/*_arm64(对应ios移动端)*/ 
#     define ISA_MASK        0x0000000ffffffff8ULL
#     define ISA_MAGIC_MASK  0x000003f000000001ULL
#     define ISA_MAGIC_VALUE 0x000001a000000001ULL
#     define ISA_HAS_CXX_DTOR_BIT 1
#     define ISA_BITFIELD                                                     \uintptr_t nonpointer        : 1; /*是否使用非指针格式*/                                      \uintptr_t has_assoc         : 1; /*是否有关联对象*/                                      \uintptr_t has_cxx_dtor      : 1; /*是否有C++析构函数*/                                      \uintptr_t shiftcls          : 33; /*MACH_VM_MAX_ADDRESS 0x1000000000*/ /*存储类指针的核心字段(加密后)*/ \uintptr_t magic             : 6; /*​​验证指针合法性​​ 调试器将提取的 magic_value 与预定义的合法值(ISA_MAGIC_VALUE)比较*/                                      \uintptr_t weakly_referenced : 1;  /*是否为弱饮用*/                                     \uintptr_t unused            : 1;  /*保留位(数据结构或协议中预留的未使用位)*/                                     \uintptr_t has_sidetable_rc  : 1;  /*是否使用弱引用表*/                                     \uintptr_t extra_rc          : 19  /*内联引用计数(最大2^19-1)*/# elif __x86_64__ //__x86_64__(对应macos)(与上面一样)
#   define ISA_MASK        0x00007ffffffffff8ULL
#   define ISA_MAGIC_MASK  0x001f800000000001ULL
#   define ISA_MAGIC_VALUE 0x001d800000000001ULL
#   define ISA_HAS_CXX_DTOR_BIT 1
#   define ISA_BITFIELD                                                        \uintptr_t nonpointer        : 1;                                         \uintptr_t has_assoc         : 1;                                         \uintptr_t has_cxx_dtor      : 1;                                         \uintptr_t shiftcls          : 44; /*MACH_VM_MAX_ADDRESS 0x7fffffe00000*/ \uintptr_t magic             : 6;                                         \uintptr_t weakly_referenced : 1;                                         \uintptr_t unused            : 1;                                         \uintptr_t has_sidetable_rc  : 1;                                         \uintptr_t extra_rc          : 8  /*(最大为2^8-1)*/

​​(1) 公共字段(所有架构)​​

  • nonpointer有两个值,表示自定义的类等,占1位
    0:纯isa指针
    1:不只是类对象地址,isa中包含了类信息、对象的引用计数等
  • has_assoc标记对象是否有关联对象,占1位。
    0:没有关联对象
    1:有关联对象
  • has_cxx_dtor标记对象是否有 C++ 析构函数(用于自动调用 dealloc),占1位
    如果有析构函数,则需要做析构逻辑
    如果没有,则可以更快的释放对象
  • magic存储魔术字(如 0x001d800000000001ULL),用于验证 isa 指针合法性,占6位。
  • weakly_referenced标记对象是否为弱引用,占1位。
  • ​​has_sidetable_rc标记是否使用弱引用表存储引用计数(当 extra_rc 溢出时启用),占1位。

(2)架构差异字段​​

  • shiftcls(ARM64: 33 位 / x86_64: 44 位)​​存储加密后的类指针。
    ​​ARM64​​:33 位(地址空间较小,0x1000000000 是 MACH_VM_MAX_ADDRESS)。
    ​​x86_64​​:44 位(地址空间较大,0x7fffffe00000 是 MACH_VM_MAX_ADDRESS)。
    ​​
  • extra_rc(ARM64: 19 位 / x86_64: 8 位)​内联存储引用计数。
    ​​ARM64​​:最大值为 2^19-1(524,287)。
    ​​x86_64​​:最大值为 2^8-1(255)。
    ​​溢出处理​​:当引用计数超过阈值时,迁移到全局弱引用表(has_sidetable_rc 置 1)。

请添加图片描述

原理探索

  通过alloc --> _objc_rootAlloc --> callAlloc --> _objc_rootAllocWithZone --> _class_createInstanceFromZone方法路径,查找到initInstanceIsa,并进入其原理实现:
在这里插入图片描述

initInstanceIsa的底层实现代码如下:

inline void 
objc_object::initInstanceIsa(Class cls, bool hasCxxDtor)
{ASSERT(!cls->instancesRequireRawIsa());ASSERT(hasCxxDtor == cls->hasCxxDtor());//初始化isainitIsa(cls, true, hasCxxDtor);
}

这里我们主要看一下初始化isa的底层代码:

inline void 
objc_object::initIsa(Class cls, bool nonpointer, UNUSED_WITHOUT_INDEXED_ISA_AND_DTOR_BIT bool hasCxxDtor)
{ ASSERT(!isTaggedPointer()); isa_t newisa(0);//isa_t是联合体,这里创建全零的isa实例if (!nonpointer) {newisa.setClass(cls, this);//设置类指针(加密存储到shiftcls)} else {ASSERT(!DisableNonpointerIsa);ASSERT(!cls->instancesRequireRawIsa());#if SUPPORT_INDEXED_ISA //!nonpointer执行的流程,即isa通过cls定义ASSERT(cls->classArrayIndex() > 0);newisa.bits = ISA_INDEX_MAGIC_VALUE;// isa.magic is part of ISA_MAGIC_VALUE// isa.nonpointer is part of ISA_MAGIC_VALUEnewisa.has_cxx_dtor = hasCxxDtor;newisa.indexcls = (uintptr_t)cls->classArrayIndex();
#else //bits执行的流程newisa.bits = ISA_MAGIC_VALUE; //bits进行赋值// isa.magic is part of ISA_MAGIC_VALUE// isa.nonpointer is part of ISA_MAGIC_VALUE
#   if ISA_HAS_CXX_DTOR_BITnewisa.has_cxx_dtor = hasCxxDtor;
#   endifnewisa.setClass(cls, this);
#endif
#if ISA_HAS_INLINE_RCnewisa.extra_rc = 1;
#endif}// This write must be performed in a single store in some cases// (for example when realizing a class because other threads// may simultaneously try to use the class).// fixme use atomics here to guarantee single-store and to// guarantee memory order w.r.t. the class index table// ...but not too atomic because we don't want to hurt instantiationisa() = newisa;
}

这里初始化isa的底层代码逻辑主要分为两部分:通过 cls 初始化 isa和通过 bits 初始化 isa。

isa与类的关联

  cls 与 isa 关联原理就是isa指针中的shiftcls位域中存储了类信息,其中initInstanceIsa的过程是将 calloc 指针 和当前的 类cls 关联起来。我们这里主要通过object_getClass函数来验证以上结论。

Objective-C 运行时提供了标准 API object_getClass,它会自动处理 isa 指针的解析(包括标签指针、元类层级等),返回对象的实际类对象。

我们在main文件中导入:

#import <objc/runtime.h>
#import "MyClass.h"

然后编写主函数文件,通过object_getClass方法来验证cls与isa是否关联:

int main(int argc, const char * argv[]) {@autoreleasepool {Class expectedCls = [MyClass class];//预期对象创建id obj = [[MyClass alloc] init];//创建实例Class actualCls = object_getClass(obj);//通过isa获取实例类// 输出验证结果NSLog(@"\n=== 类关联验证 ===");NSLog(@"预期类: %@", NSStringFromClass(expectedCls));NSLog(@"实际类: %@", NSStringFromClass(actualCls));// 断言验证if (expectedCls != actualCls) {NSLog(@"\033[31m❌ 验证失败:类关联错误!\033[0m");NSLog(@"预期类: %@", NSStringFromClass(expectedCls));NSLog(@"实际类: %@", NSStringFromClass(actualCls));assert(NO); // 明确触发断言} else {NSLog(@"\033[32m✅ 验证成功:类关联正确\033[0m");}}return 0;
}

运行代码,输出如下:请添加图片描述
这证明cls 与isa已经完美关联。

总结

  这篇文章主要探究了isa与类的关联相关内容,在代码调试过程中,笔者出现了很多问题,许多地方的代码调试步骤跟预期不一样,可能是因为obj源码的版本不同,文章中若有错误,还请斧正。

参考文章:iOS-底层原理 07:isa与类关联的原理

相关文章:

【iOS】源码阅读(四)——isa与类关联的原理

文章目录 前言OC对象本质探索clang探索对象本质objc_setProperty源码探索 cls与类的关联原理为什么说bits与cls为互斥关系isa的类型isa_t原理探索isa与类的关联 总结 前言 本篇文章主要是笔者在学习和理解类与isa的关联关系时所写的笔记。 OC对象本质探索 在学习和理解类与isa…...

elementUI 循环出来的表单,怎么做表单校验?

数据结构如下&#xff1a; diversionParamList: [ { length: null, positionNumber: null, value: null, } ] 思路&#xff1a;可根据 index 动态绑定 :props 属性值&#xff0c;校验规则写在:rules <div class"config-item" v-for"(item, index) in form.…...

蓝桥杯11届国B 约数

题目描述 定义阶乘 n!123⋅⋅⋅n。 请问 100! &#xff08;100 的阶乘&#xff09;有多少个正约数 100! 是所有从 1 到 100 的数的乘积&#xff0c;因此&#xff1a; 质数 j 在 100! 中的总指数 质数 j 在 1 中的指数 质数 j 在 2 中的指数 ... 质数 j 在 100 中的指数 …...

【iOS】alloc的实际流程

目录 前言 为什么不按源码流程调用&#xff1f; alloc的调用流程 前言 在之前的博客中我们有学习到过alloc的底层原理&#xff0c;沿着源码一步步找到了alloc的调用链——alloc—>_objc_rootAlloc—>callAlloc—>_objc_rootAllocWithZone—>_class_createInstan…...

Maven clean 提示文件 java.io.IOException

Maven clean 提示文件 java.io.IOException 问题背景问题分析&处理问题总结 问题背景 今天在一个功能开发结束之后&#xff0c;准备通过Maven实现打包操作&#xff0c;然后打包完成后部署到测试环境进行测试。在IDEA开发工具&#xff0c;点击 clean 时提示 Failed to dele…...

QT 使用QPdfWriter和QPainter绘制PDF文件

QT如何生产pdf文件&#xff0c;网上有许多文章介绍&#xff0c;我也是看了网上的文章&#xff0c;看他们的代码&#xff0c;自己琢磨琢磨&#xff0c;才有了本编博客&#xff1b; 其他什么就不详细说了&#xff0c;本篇博客介绍的QPdfWriter和QPainter绘制PDF文件&#xff1b;…...

解密企业级大模型智能体Agentic AI 关键技术:MCP、A2A、Reasoning LLMs- Manus技术解密

解密企业级大模型智能体Agentic AI 关键技术&#xff1a;MCP、A2A、Reasoning LLMs- Manus技术解密 如果你从应用程序的角度讲&#xff0c;但是如果我们从模型的角度讲&#xff0c;我们必须让模型既具有这种思考的能力&#xff0c;也具有产出这种最佳的action的这种能力。而且…...

网络安全-等级保护(等保) 2-4 GB/T 22239-2019 《信息安全技术 网络安全等级保护基础要求》-2019-05-10发布【现行】

################################################################################ 等级确定之后&#xff0c;需要根据不同的安全等级满足相关建设要求&#xff0c;《等级保护基础要求》明确了安全物理环境、安全通信网络、安全区域边界、安全计算环境、安全管理中心、安全管…...

Ansys Zemax | 在 MATLAB 或 Python 中使用 ZOS-API 进行光线追迹的批次处理

附件下载 联系工作人员获取附件 这篇文章会说明如何在 MATLAB 或 Python 中以 Zemax OpticStudio 应用程式界面 (ZOS-API)处理光线数据库(Ray Database, ZRD)档案&#xff0c;过程中我们将使用ZRDLoader.dll。本文提供了在 Matlab 中批次处理序列光线追迹(一般、归一化、偏振…...

多链互操作性标准解析:构建下一代区块链互联生态

引言 在区块链技术快速演进的今天&#xff0c;“多链宇宙”已成为不可逆的趋势。然而&#xff0c;链与链之间的孤立性导致流动性割裂、开发成本高昂和用户体验碎片化。互操作性标准的制定&#xff0c;正是打破这一僵局的核心钥匙。本文将深入探讨主流互操作性协议的技术架构、…...

openEuler24.03 LTS下安装MySQL8.0.42

目录 前提步骤 删除原有mysql及maridb数据库 安装MySQL 启动MySQL 启动查看MySQL状态 设置MySQL开机自启动 查看登录密码 登录MySQL 修改密码及支持远程连接 远程连接MySQL 前提步骤 拥有openEuler24.03 LTS环境&#xff0c;可参考&#xff1a;Vmware下安装openEule…...

React 轻量级富文本编辑器推荐(中文版)

以下是几款适合集成到 React 项目中的轻量级富文本编辑器&#xff0c;特别针对中文用户优化推荐&#xff1a; 超轻量级选择&#xff08;小于100KB&#xff09; 1. react-simplemde-editor&#xff08;Markdown编辑器&#xff09; 特点&#xff1a;专为 Markdown 设计&#xf…...

React 第四十一节Router 中 useActionData 使用方法案例以及注意事项

一、useActionData前言 useActionData 是 React Router 提供的一个钩子函数&#xff0c;用于获取在路由的 action 函数中返回的数据。它通常与表单提交&#xff08;通过 <Form> 组件&#xff09;配合使用&#xff0c;用于处理表单提交后的服务器响应数据&#xff08;如错…...

西门子 Teamcenter13 Eclipse RCP 开发 1 工具栏

西门子 Teamcenter13 Eclipse RCP 开发 1 工具栏 1 配置文件2 插件控制3 命令框架 1 配置文件 在 Teamcenter 13 Eclipse RCP 开发中&#xff0c;plugin.xml 是插件的核心配置文件&#xff0c;定义了插件的&#xff1a; 1、唯一身份&#xff08;ID、版本&#xff09;。 2、所…...

【python实用小脚本-63】每天花费2小时修复黑白照片,Python一键转换,节省90%时间(建议收藏)

一、应用场景故事 上周&#xff0c;我的朋友小李从家里翻出了一堆老照片&#xff0c;这些照片大多是彩色的&#xff0c;但他想把它们转换成黑白风格&#xff0c;让照片更有复古感。他尝试用Photoshop一张张处理&#xff0c;但花了整整一个周末&#xff0c;才处理了不到一半的照…...

R语言的专业网站top5推荐

李升伟 以下是学习R语言的五个顶级专业网站推荐&#xff0c;涵盖教程、社区、资源库和最新动态&#xff1a; 1.R项目官网 (r-project.org) R语言的官方网站&#xff0c;提供软件下载、文档、手册和常见问题解答。特别适合初学者和高级用户&#xff0c;是获取R语言核心资源的…...

Apache JMeter API 接口压测技术指南

文章目录 前言技术积累JMeter 简介适用场景JMeter 核心组件 安装与配置线程组压测逐步加压压测安装相应jmeter 插件创建测试计划生成压测HTML 总结 前言 Apache JMeter 是一款开源的性能测试工具&#xff0c;主要用于对 Web 应用、API 接口、数据库等进行负载和压力测试。本指…...

【Java实战】IO流(转换流,打印流,数据流,序列化流)

引出问题 不同编码读取出现的乱码问题 如果是代码编码被读取的文本文件的编码是一致的&#xff0c;使用字符流读取文本文件时不会出现乱码&#xff1b; 如果不一致&#xff0c;使用字符流读取文本文件就会出现乱码。 public class BufferedReader{ public static void main(S…...

Python课程及开源项目推荐

Python课程及开源项目推荐 摘要&#xff1a;学习 Python 是一个非常好的选择&#xff0c;因为它是一种功能强大且易于上手的编程语言&#xff0c;广泛应用于数据分析、数据可视化、机器学习、网络爬虫等领域。以下是针对 Coursera 上 Python 课程和专业证书的推荐&#xff0c;以…...

内网互通原则详解!

目录 前言1. 路由 (Routing): 谁去哪儿找谁&#xff1f;&#x1f5fa;️2. 防火墙与安全组 (Firewalls & Security Groups): 门卫大爷和保安系统&#xff01;&#x1f46e;‍♂️&#x1f6aa;3. 内网 DNS (Internal DNS): 小区的通讯录/电话本&#xff01;&#x1f4d2;&a…...

[Harmony]大文件持久化

1.添加权限 在module.json5文件中添加权限 "requestPermissions": [{"name": "ohos.permission.READ_WRITE_USER_FILE", // 读写用户数据"reason": "$string:read_write_user_file_reason","usedScene": {"…...

C 语言实战:使用二维数组进行学生成绩统计与分析

各类资料学习下载合集 ​​https://pan.quark.cn/s/8c91ccb5a474​​ 在处理表格型数据时,二维数组是 C 语言中一种非常直观且强大的工具。学生成绩单就是一个典型的二维数据:每一行代表一个学生,每一列代表一门科目。本文将通过一个具体的案例,演示如何利用二维数组来存…...

[学习]RTKLib详解:tle.c(系列终章)

本文是 RTKLlib详解 系列文章的一篇&#xff0c;目前该系列文章还在持续总结写作中&#xff0c;以发表的如下&#xff0c;有兴趣的可以翻阅。 [学习] RTKlib详解&#xff1a;功能、工具与源码结构解析 [学习]RTKLib详解&#xff1a;pntpos.c与postpos.c [学习]RTKLib详解&…...

体重秤出口日本的计量认证介绍,体脂秤出口日本“正”认证介绍

什么是家庭专用测量仪器&#xff1f; 家庭专用测量仪器是一种非自动秤&#xff0c;主要用于普通消费者的日常生活&#xff0c;并且刻度为10 mg或更大&#xff0c;并且秤号的刻度为100或更大&#xff0c;满足以下条件的&#xff1a; 通用秤 是一种非自动秤&#xff0c;重量超过…...

【hadoop】Flume的相关介绍

1 概述 Flume是Cloudera开发的一个分布式的、可靠的、高可用的系统&#xff0c;它能够将不同数据源的海量日志数据进行高效收集、聚合、移动&#xff0c;最后存储到一个中心化的数据存储系统中。随着互联网的发展&#xff0c;特别是移动互联网的兴起&#xff0c;产生了海量的用…...

图片、音频、视频都能转?简鹿格式工厂了解一下

我们每天都会接触到各种各样的音视频和图片文件。无论是拍摄的照片、录制的视频&#xff0c;还是下载的音频资源&#xff0c;它们往往以不同的格式存在——有些适合分享&#xff0c;有些适合编辑&#xff0c;而有些则仅限特定设备或平台使用。格式不统一的问题&#xff0c;正在…...

doris节点数量规划

1.FE 节点数量​ FE 节点主要负责用户请求的接入、查询解析规划、元数据管理及节点管理等工作。 对于生产集群&#xff0c;一般建议部署至少 3 个节点的 FE 以实现高可用环境。FE 节点分为以下两种角色&#xff1a; Follower 节点&#xff1a;参与选举操作&#xff0c…...

Android Studio中Gradle 7.0上下项目配置及镜像修改

最近在打包一个测试项目的时候&#xff0c;使用的android studio版本比较老&#xff0c;后来升级到2024发现了一些小问题。可能对于安卓程序员来说&#xff0c;司空见惯&#xff0c;但对我这样的安卓小白&#xff0c;可把我折腾了半天。现在记录下来&#xff0c;供比我还小白的…...

Android Studio中Gradle中Task列表显示不全解决方案

问题现象 解决方案 File -> Settings -> Experimental ->勾选Configure all Gradle tasks during Gradle Sync(this can make Gradle Sync slower) 参考文章 Android执行build-gradle中的任务Task...

OneNote内容太多插入标记卡死的解决办法

OneNote内容太多插入标记卡死的解决办法 针对平板电脑的OneNote用户适合此类情况&#xff1a; 当向电脑导入几百页pdf可以正常使用&#xff0c;唯独插入标记的时候OneNote直接罢工&#xff0c;只能关闭。关闭时还可能会出现0x000000fxxxxx的错误。 注&#xff1a;仅对于平板…...

vue3:十三、分类管理-表格--行内按钮---行删除、批量删除实现功能实现

一、实现效果 增加行内按钮的样式效果,并且可以根绝父组件决定是否显示 增加行内删除功能、批量删除功能 二、增加行内按钮样式 1、增加视图层按钮 由于多个表格都含有按钮功能,所以这里直接在子组件中加入插槽按钮 首先增加表格行<el-table-column></el-table-…...

Python类的力量:第五篇:魔法方法与协议——让类拥有Python的“超能力”

文章目录 前言&#xff1a;从“普通对象”到“Python原生公民”的进化之路 一、魔法方法&#xff1a;赋予对象“超能力”的基因1. 构造与析构&#xff1a;对象生命周期的“魔法开关”2. 字符串表示&#xff1a;对象的“自我介绍”3. 运算符重载&#xff1a;让对象支持“数学魔法…...

R S的EMI接收机面板

图片摘自R & S官网。 根据您提供的第一张图&#xff08;设备前面板带屏幕的图像&#xff09;&#xff0c;这是 Rohde & Schwarz ESRP7 EMI Test Receiver 的正面显示界面&#xff0c;我将对屏幕上显示的参数逐项进行解读&#xff1a; &#x1f5a5;️ 屏幕参数解读 左…...

pytorch nn.RNN demo

之前已经讲过关于RNNCell的实现了. 这里用LLM写了一个简单的nn.RNN demo&#xff1a; import torch import torch.nn as nn# 设置随机种子以便结果可复现 torch.manual_seed(42)# 定义模型参数 input_size 4 # 输入特征维度 hidden_size 8 # 隐藏层维度 num_layer…...

高防服务器流量“清洗”什么意思

在当今数字化的时代&#xff0c;网络安全成为了备受关注的焦点。其中&#xff0c;高防服务器流量“清洗”这个概念&#xff0c;对于许多朋友来说可能还比较陌生。今天&#xff0c;就让我们一起来揭开它神秘的面纱。 首先&#xff0c;咱们得明白&#xff0c;高防服务器流量“清…...

Unity3D开发AI桌面精灵/宠物系列 【六】 人物模型 语音口型同步 LipSync 、梅尔频谱MFCC技术、支持中英文自定义编辑- 基于 C# 语言开发

Unity3D开发AI桌面精灵/宠物系列 【六】 人物模型 语音口型同步 LipSync 、梅尔频谱MFCC技术 C# 语言开发 该系列主要介绍怎么制作AI桌面宠物的流程&#xff0c;我会从项目开始创建初期到最终可以和AI宠物进行交互为止&#xff0c;项目已经开发完成&#xff0c;我会仔细梳理一下…...

Java详解LeetCode 热题 100(17):LeetCode 41. 缺失的第一个正数(First Missing Positive)详解

文章目录 1. 题目描述2. 理解题目3. 解法一&#xff1a;排序法&#xff08;不满足题目要求&#xff09;3.1 思路3.2 Java代码实现3.3 代码详解3.4 复杂度分析3.5 不足之处 4. 解法二&#xff1a;哈希表法4.1 思路4.2 Java代码实现4.3 代码详解4.4 复杂度分析4.5 不足之处 5. 解…...

Kafka消息路由分区机制深度解析:架构设计与实现原理

一、消息路由系统的核心架构哲学 1.1 分布式系统的三元悖论 在分布式消息系统的设计过程中&#xff0c;架构师需要平衡三个核心诉求&#xff1a;数据一致性、系统可用性和分区容忍性。Kafka的分区路由机制本质上是对CAP定理的实践解&#xff1a; 一致性维度&#xff1a;通过…...

用C语言实现了——一个基于顺序表的插入排序演示系统

一、知识要点、 插入排序是一种简单直观的排序算法&#xff0c;它的工作方式类似于我们整理扑克牌。 基本原理&#xff1a; 插入排序通过构建有序序列来工作。它每次从无序序列中取出一个元素&#xff0c;然后将其插入到已排序序列的适当位置。这个过程重复进行&#xff0c;…...

linux libdbus使用案例

以下是一个基于 Linux libdbus 的详细指南,包含服务端和客户端的完整代码示例,涵盖 方法调用、信号发送 和 异步消息处理。libdbus 是 D-Bus 的底层 C 库,直接操作 D-Bus 协议,适合需要精细控制的场景。 1. libdbus 的核心机制 连接管理:通过 dbus_bus_get 连接系统总线或…...

Apple Vision Pro空间视频创作革命:从180度叙事到沉浸式语法的重构——《Adventure》系列幕后技术深度解析

🌌 引言:沉浸式媒体的“语法实验室” Apple Vision Pro的推出标志着空间计算时代的到来,而《Adventure》系列作为其原生内容标杆,正在成为沉浸式叙事的“语法实验室”。导演Charlotte Mikkelborg与播客主持人Kent Bye的对话揭示了这一领域的技术突破、创作挑战与行业生态…...

[特殊字符] 苍穹外卖项目中的 WebSocket 实战:实现来单与催单提醒功能

&#x1f680; 苍穹外卖项目中的 WebSocket 实战&#xff1a;实现来单与催单提醒功能 在现代 Web 应用中&#xff0c;实时通信成为提升用户体验的关键技术之一。WebSocket 作为一种在单个 TCP 连接上进行全双工通信的协议&#xff0c;被广泛应用于需要实时数据交换的场景&#…...

【C/C++】深度解析C++ Allocator:优化内存管理的关键

文章目录 深度解析C Allocator&#xff1a;优化内存管理的关键1 默认 std::allocator2 自定义 Allocator3 自定义 Allocator 的实现3.1 基本结构3.2 使用自定义 Allocator 4 关键特性详解4.1 rebind 机制4.2 状态化 Allocator 5 应用示例&#xff1a;内存池 Allocator5.1 简单内…...

gitlab+portainer 实现Ruoyi Vue前端CI/CD

1. 场景 最近整了一个Ruoyi Vue 项目&#xff0c;需要实现CICD&#xff0c;经过一番坎坷&#xff0c;最终达成&#xff0c;现将技术要点和踩坑呈现。 具体操作流程和后端大同小异&#xff0c;后端操作参考连接如下&#xff1a; https://blog.csdn.net/leinminna/article/detai…...

CAPL编程系列_04

1_ 测试模块TestModule&#xff1a;基本使用 1&#xff09;在Simulation Setup 中创建并配置 Test Module节点 2&#xff09;编写测试脚本 【1】测试用例函数&#xff08;testcase&#xff09;:实现具体测试逻辑 【2】主测试函数&#xff08;Main Test&#xff09;&…...

Weblogic SSRF漏洞复现(CVE-2014-4210)【vulhub靶场】

漏洞概述&#xff1a; Weblogic中存在一个SSRF漏洞&#xff0c;利用该漏洞可以发送任意HTTP请求&#xff0c;进而攻击内网中redis、fastcgi等脆弱组件。 漏洞形成原因&#xff1a; WebLogic Server 的 UDDI 组件&#xff08;uddiexplorer.war&#xff09;中的 SearchPublicR…...

科技的成就(六十八)

623、杰文斯悖论 杰文斯悖论是1865年经济学家威廉斯坦利杰文斯提出的一悖论&#xff1a;当技术进步提高了效率&#xff0c;资源消耗不仅没有减少&#xff0c;反而激增。例如&#xff0c;瓦特改良的蒸汽机让煤炭燃烧更加高效&#xff0c;但结果却是煤炭需求飙升。 624、代码混…...

知从科技闪耀2025上海车展:以创新驱动未来出行新篇章

上海&#xff0c;2025年4月23日——全球汽车科技领域的年度盛会——2025上海国际汽车工业展览会&#xff08;简称“上海车展”&#xff09;于5月2日圆满落幕。作为智能汽车软件与系统解决方案的领军企业&#xff0c;知从科技受邀参展&#xff0c;并在活动期间全方位展示了其在智…...

【iOS安全】Dopamine越狱 iPhone X iOS 16.6 (20G75) | 解决Jailbreak failed with error

Dopamine越狱 iPhone X iOS 16.6 (20G75) Dopamine兼容设备 参考&#xff1a;https://www.bilibili.com/opus/977469285985157129 A9 - A11&#xff08;iPhone6s&#xff0d;X&#xff09;&#xff1a;iOS15.0-16.6.1 A12-A14&#xff08;iPhoneXR&#xff0d;12PM&#xf…...

医疗数据迁移质量与效率的深度研究:三维六阶框架与实践创新

引言 随着医疗信息化建设的深入推进,医疗数据作为医疗机构的核心资产,其价值与日俱增。在医院信息系统升级、迁移或整合过程中,数据迁移的质量与效率直接关系到医疗服务的连续性、患者信息的安全性以及医院运营的稳定性。传统数据迁移方法往往面临时间长、风险高、成本大等…...