【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);
总结
由以上我们可以得出:
- OC对象的本质就是结构体。
- 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);
}
所以通过上述代码,我们可以总结出其调用链:
- 编译器生成 setName: 方法,调用 objc_setProperty。
- objc_setProperty 内部调用 reallySetProperty。
- 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的内存管理规则
实际应用
- 优先使用copy修饰不可变对象:
@property (nonatomic, copy) NSString *text;
@property (nonatomic, copy) NSArray *items;
避免传入 NSMutableString/NSMutableArray 后,原属性值被外部修改。
- 谨慎使用mutableCopy修饰不可变对象:
除非明确需要可变副本,否则直接用 NSMutableArray 等类型。 - 自定义类支持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);
这里的输出如下:
为什么会出现这种情况呢?我们来逐行解析一下以上代码:
- 首先我们给u.a赋值为42,这是内存中存储的是整数值42的二进制表示。
- 然后给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 循环出来的表单,怎么做表单校验?
数据结构如下: diversionParamList: [ { length: null, positionNumber: null, value: null, } ] 思路:可根据 index 动态绑定 :props 属性值,校验规则写在:rules <div class"config-item" v-for"(item, index) in form.…...
蓝桥杯11届国B 约数
题目描述 定义阶乘 n!123⋅⋅⋅n。 请问 100! (100 的阶乘)有多少个正约数 100! 是所有从 1 到 100 的数的乘积,因此: 质数 j 在 100! 中的总指数 质数 j 在 1 中的指数 质数 j 在 2 中的指数 ... 质数 j 在 100 中的指数 …...
【iOS】alloc的实际流程
目录 前言 为什么不按源码流程调用? alloc的调用流程 前言 在之前的博客中我们有学习到过alloc的底层原理,沿着源码一步步找到了alloc的调用链——alloc—>_objc_rootAlloc—>callAlloc—>_objc_rootAllocWithZone—>_class_createInstan…...
Maven clean 提示文件 java.io.IOException
Maven clean 提示文件 java.io.IOException 问题背景问题分析&处理问题总结 问题背景 今天在一个功能开发结束之后,准备通过Maven实现打包操作,然后打包完成后部署到测试环境进行测试。在IDEA开发工具,点击 clean 时提示 Failed to dele…...
QT 使用QPdfWriter和QPainter绘制PDF文件
QT如何生产pdf文件,网上有许多文章介绍,我也是看了网上的文章,看他们的代码,自己琢磨琢磨,才有了本编博客; 其他什么就不详细说了,本篇博客介绍的QPdfWriter和QPainter绘制PDF文件;…...
解密企业级大模型智能体Agentic AI 关键技术:MCP、A2A、Reasoning LLMs- Manus技术解密
解密企业级大模型智能体Agentic AI 关键技术:MCP、A2A、Reasoning LLMs- Manus技术解密 如果你从应用程序的角度讲,但是如果我们从模型的角度讲,我们必须让模型既具有这种思考的能力,也具有产出这种最佳的action的这种能力。而且…...
网络安全-等级保护(等保) 2-4 GB/T 22239-2019 《信息安全技术 网络安全等级保护基础要求》-2019-05-10发布【现行】
################################################################################ 等级确定之后,需要根据不同的安全等级满足相关建设要求,《等级保护基础要求》明确了安全物理环境、安全通信网络、安全区域边界、安全计算环境、安全管理中心、安全管…...
Ansys Zemax | 在 MATLAB 或 Python 中使用 ZOS-API 进行光线追迹的批次处理
附件下载 联系工作人员获取附件 这篇文章会说明如何在 MATLAB 或 Python 中以 Zemax OpticStudio 应用程式界面 (ZOS-API)处理光线数据库(Ray Database, ZRD)档案,过程中我们将使用ZRDLoader.dll。本文提供了在 Matlab 中批次处理序列光线追迹(一般、归一化、偏振…...
多链互操作性标准解析:构建下一代区块链互联生态
引言 在区块链技术快速演进的今天,“多链宇宙”已成为不可逆的趋势。然而,链与链之间的孤立性导致流动性割裂、开发成本高昂和用户体验碎片化。互操作性标准的制定,正是打破这一僵局的核心钥匙。本文将深入探讨主流互操作性协议的技术架构、…...
openEuler24.03 LTS下安装MySQL8.0.42
目录 前提步骤 删除原有mysql及maridb数据库 安装MySQL 启动MySQL 启动查看MySQL状态 设置MySQL开机自启动 查看登录密码 登录MySQL 修改密码及支持远程连接 远程连接MySQL 前提步骤 拥有openEuler24.03 LTS环境,可参考:Vmware下安装openEule…...
React 轻量级富文本编辑器推荐(中文版)
以下是几款适合集成到 React 项目中的轻量级富文本编辑器,特别针对中文用户优化推荐: 超轻量级选择(小于100KB) 1. react-simplemde-editor(Markdown编辑器) 特点:专为 Markdown 设计…...
React 第四十一节Router 中 useActionData 使用方法案例以及注意事项
一、useActionData前言 useActionData 是 React Router 提供的一个钩子函数,用于获取在路由的 action 函数中返回的数据。它通常与表单提交(通过 <Form> 组件)配合使用,用于处理表单提交后的服务器响应数据(如错…...
西门子 Teamcenter13 Eclipse RCP 开发 1 工具栏
西门子 Teamcenter13 Eclipse RCP 开发 1 工具栏 1 配置文件2 插件控制3 命令框架 1 配置文件 在 Teamcenter 13 Eclipse RCP 开发中,plugin.xml 是插件的核心配置文件,定义了插件的: 1、唯一身份(ID、版本)。 2、所…...
【python实用小脚本-63】每天花费2小时修复黑白照片,Python一键转换,节省90%时间(建议收藏)
一、应用场景故事 上周,我的朋友小李从家里翻出了一堆老照片,这些照片大多是彩色的,但他想把它们转换成黑白风格,让照片更有复古感。他尝试用Photoshop一张张处理,但花了整整一个周末,才处理了不到一半的照…...
R语言的专业网站top5推荐
李升伟 以下是学习R语言的五个顶级专业网站推荐,涵盖教程、社区、资源库和最新动态: 1.R项目官网 (r-project.org) R语言的官方网站,提供软件下载、文档、手册和常见问题解答。特别适合初学者和高级用户,是获取R语言核心资源的…...
Apache JMeter API 接口压测技术指南
文章目录 前言技术积累JMeter 简介适用场景JMeter 核心组件 安装与配置线程组压测逐步加压压测安装相应jmeter 插件创建测试计划生成压测HTML 总结 前言 Apache JMeter 是一款开源的性能测试工具,主要用于对 Web 应用、API 接口、数据库等进行负载和压力测试。本指…...
【Java实战】IO流(转换流,打印流,数据流,序列化流)
引出问题 不同编码读取出现的乱码问题 如果是代码编码被读取的文本文件的编码是一致的,使用字符流读取文本文件时不会出现乱码; 如果不一致,使用字符流读取文本文件就会出现乱码。 public class BufferedReader{ public static void main(S…...
Python课程及开源项目推荐
Python课程及开源项目推荐 摘要:学习 Python 是一个非常好的选择,因为它是一种功能强大且易于上手的编程语言,广泛应用于数据分析、数据可视化、机器学习、网络爬虫等领域。以下是针对 Coursera 上 Python 课程和专业证书的推荐,以…...
内网互通原则详解!
目录 前言1. 路由 (Routing): 谁去哪儿找谁?🗺️2. 防火墙与安全组 (Firewalls & Security Groups): 门卫大爷和保安系统!👮♂️🚪3. 内网 DNS (Internal DNS): 小区的通讯录/电话本!📒&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详解 系列文章的一篇,目前该系列文章还在持续总结写作中,以发表的如下,有兴趣的可以翻阅。 [学习] RTKlib详解:功能、工具与源码结构解析 [学习]RTKLib详解:pntpos.c与postpos.c [学习]RTKLib详解&…...
体重秤出口日本的计量认证介绍,体脂秤出口日本“正”认证介绍
什么是家庭专用测量仪器? 家庭专用测量仪器是一种非自动秤,主要用于普通消费者的日常生活,并且刻度为10 mg或更大,并且秤号的刻度为100或更大,满足以下条件的: 通用秤 是一种非自动秤,重量超过…...
【hadoop】Flume的相关介绍
1 概述 Flume是Cloudera开发的一个分布式的、可靠的、高可用的系统,它能够将不同数据源的海量日志数据进行高效收集、聚合、移动,最后存储到一个中心化的数据存储系统中。随着互联网的发展,特别是移动互联网的兴起,产生了海量的用…...
图片、音频、视频都能转?简鹿格式工厂了解一下
我们每天都会接触到各种各样的音视频和图片文件。无论是拍摄的照片、录制的视频,还是下载的音频资源,它们往往以不同的格式存在——有些适合分享,有些适合编辑,而有些则仅限特定设备或平台使用。格式不统一的问题,正在…...
doris节点数量规划
1.FE 节点数量 FE 节点主要负责用户请求的接入、查询解析规划、元数据管理及节点管理等工作。 对于生产集群,一般建议部署至少 3 个节点的 FE 以实现高可用环境。FE 节点分为以下两种角色: Follower 节点:参与选举操作,…...
Android Studio中Gradle 7.0上下项目配置及镜像修改
最近在打包一个测试项目的时候,使用的android studio版本比较老,后来升级到2024发现了一些小问题。可能对于安卓程序员来说,司空见惯,但对我这样的安卓小白,可把我折腾了半天。现在记录下来,供比我还小白的…...
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用户适合此类情况: 当向电脑导入几百页pdf可以正常使用,唯独插入标记的时候OneNote直接罢工,只能关闭。关闭时还可能会出现0x000000fxxxxx的错误。 注:仅对于平板…...
vue3:十三、分类管理-表格--行内按钮---行删除、批量删除实现功能实现
一、实现效果 增加行内按钮的样式效果,并且可以根绝父组件决定是否显示 增加行内删除功能、批量删除功能 二、增加行内按钮样式 1、增加视图层按钮 由于多个表格都含有按钮功能,所以这里直接在子组件中加入插槽按钮 首先增加表格行<el-table-column></el-table-…...
Python类的力量:第五篇:魔法方法与协议——让类拥有Python的“超能力”
文章目录 前言:从“普通对象”到“Python原生公民”的进化之路 一、魔法方法:赋予对象“超能力”的基因1. 构造与析构:对象生命周期的“魔法开关”2. 字符串表示:对象的“自我介绍”3. 运算符重载:让对象支持“数学魔法…...
R S的EMI接收机面板
图片摘自R & S官网。 根据您提供的第一张图(设备前面板带屏幕的图像),这是 Rohde & Schwarz ESRP7 EMI Test Receiver 的正面显示界面,我将对屏幕上显示的参数逐项进行解读: 🖥️ 屏幕参数解读 左…...
pytorch nn.RNN demo
之前已经讲过关于RNNCell的实现了. 这里用LLM写了一个简单的nn.RNN demo: import torch import torch.nn as nn# 设置随机种子以便结果可复现 torch.manual_seed(42)# 定义模型参数 input_size 4 # 输入特征维度 hidden_size 8 # 隐藏层维度 num_layer…...
高防服务器流量“清洗”什么意思
在当今数字化的时代,网络安全成为了备受关注的焦点。其中,高防服务器流量“清洗”这个概念,对于许多朋友来说可能还比较陌生。今天,就让我们一起来揭开它神秘的面纱。 首先,咱们得明白,高防服务器流量“清…...
Unity3D开发AI桌面精灵/宠物系列 【六】 人物模型 语音口型同步 LipSync 、梅尔频谱MFCC技术、支持中英文自定义编辑- 基于 C# 语言开发
Unity3D开发AI桌面精灵/宠物系列 【六】 人物模型 语音口型同步 LipSync 、梅尔频谱MFCC技术 C# 语言开发 该系列主要介绍怎么制作AI桌面宠物的流程,我会从项目开始创建初期到最终可以和AI宠物进行交互为止,项目已经开发完成,我会仔细梳理一下…...
Java详解LeetCode 热题 100(17):LeetCode 41. 缺失的第一个正数(First Missing Positive)详解
文章目录 1. 题目描述2. 理解题目3. 解法一:排序法(不满足题目要求)3.1 思路3.2 Java代码实现3.3 代码详解3.4 复杂度分析3.5 不足之处 4. 解法二:哈希表法4.1 思路4.2 Java代码实现4.3 代码详解4.4 复杂度分析4.5 不足之处 5. 解…...
Kafka消息路由分区机制深度解析:架构设计与实现原理
一、消息路由系统的核心架构哲学 1.1 分布式系统的三元悖论 在分布式消息系统的设计过程中,架构师需要平衡三个核心诉求:数据一致性、系统可用性和分区容忍性。Kafka的分区路由机制本质上是对CAP定理的实践解: 一致性维度:通过…...
用C语言实现了——一个基于顺序表的插入排序演示系统
一、知识要点、 插入排序是一种简单直观的排序算法,它的工作方式类似于我们整理扑克牌。 基本原理: 插入排序通过构建有序序列来工作。它每次从无序序列中取出一个元素,然后将其插入到已排序序列的适当位置。这个过程重复进行,…...
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 实战:实现来单与催单提醒功能
🚀 苍穹外卖项目中的 WebSocket 实战:实现来单与催单提醒功能 在现代 Web 应用中,实时通信成为提升用户体验的关键技术之一。WebSocket 作为一种在单个 TCP 连接上进行全双工通信的协议,被广泛应用于需要实时数据交换的场景&#…...
【C/C++】深度解析C++ Allocator:优化内存管理的关键
文章目录 深度解析C Allocator:优化内存管理的关键1 默认 std::allocator2 自定义 Allocator3 自定义 Allocator 的实现3.1 基本结构3.2 使用自定义 Allocator 4 关键特性详解4.1 rebind 机制4.2 状态化 Allocator 5 应用示例:内存池 Allocator5.1 简单内…...
gitlab+portainer 实现Ruoyi Vue前端CI/CD
1. 场景 最近整了一个Ruoyi Vue 项目,需要实现CICD,经过一番坎坷,最终达成,现将技术要点和踩坑呈现。 具体操作流程和后端大同小异,后端操作参考连接如下: https://blog.csdn.net/leinminna/article/detai…...
CAPL编程系列_04
1_ 测试模块TestModule:基本使用 1)在Simulation Setup 中创建并配置 Test Module节点 2)编写测试脚本 【1】测试用例函数(testcase):实现具体测试逻辑 【2】主测试函数(Main Test)&…...
Weblogic SSRF漏洞复现(CVE-2014-4210)【vulhub靶场】
漏洞概述: Weblogic中存在一个SSRF漏洞,利用该漏洞可以发送任意HTTP请求,进而攻击内网中redis、fastcgi等脆弱组件。 漏洞形成原因: WebLogic Server 的 UDDI 组件(uddiexplorer.war)中的 SearchPublicR…...
科技的成就(六十八)
623、杰文斯悖论 杰文斯悖论是1865年经济学家威廉斯坦利杰文斯提出的一悖论:当技术进步提高了效率,资源消耗不仅没有减少,反而激增。例如,瓦特改良的蒸汽机让煤炭燃烧更加高效,但结果却是煤炭需求飙升。 624、代码混…...
知从科技闪耀2025上海车展:以创新驱动未来出行新篇章
上海,2025年4月23日——全球汽车科技领域的年度盛会——2025上海国际汽车工业展览会(简称“上海车展”)于5月2日圆满落幕。作为智能汽车软件与系统解决方案的领军企业,知从科技受邀参展,并在活动期间全方位展示了其在智…...
【iOS安全】Dopamine越狱 iPhone X iOS 16.6 (20G75) | 解决Jailbreak failed with error
Dopamine越狱 iPhone X iOS 16.6 (20G75) Dopamine兼容设备 参考:https://www.bilibili.com/opus/977469285985157129 A9 - A11(iPhone6s-X):iOS15.0-16.6.1 A12-A14(iPhoneXR-12PM…...
医疗数据迁移质量与效率的深度研究:三维六阶框架与实践创新
引言 随着医疗信息化建设的深入推进,医疗数据作为医疗机构的核心资产,其价值与日俱增。在医院信息系统升级、迁移或整合过程中,数据迁移的质量与效率直接关系到医疗服务的连续性、患者信息的安全性以及医院运营的稳定性。传统数据迁移方法往往面临时间长、风险高、成本大等…...