【iOS】retain/release底层实现原理

文章目录

    • 前言
    • 前情知识
    • retain和release的实现原理(MRC手动管理)
      • retain(MRC手动管理)
        • retain源码
        • 内联函数rootRetain源码
        • 相关的sidetable_tryRetain()方法
        • retain底层工作流程总结
      • release
        • release源码
        • 内联函数rootRelease源码
    • 小结

前言

  在前面OC的学习中,我们了解到了OC中的关键字,今天我们来具体解析一下strong、copy、retain、release的实现原理,他们都是内存管理的核心机制,其底层实现深度依赖引用计数(Retain Count)运行时(Runtime)的 SideTable机制

前情知识

  在了解其底层原理之前,我们先来回顾一下引用计数的相关概念。

  OC内存管理的核心是引用计数(ARC 下由编译器自动管理,MRC 下手动操作)。每个对象有一个隐式的引用计数retainCount),表示当前有多少个“拥有者”持有该对象:

  • 引用计数 +1:对象被“持有”(如 retainstrong赋值)。
  • 引用计数 -1:对象被“释放”(如 releasestrong变量超出作用域)。
  • 引用计数为 0:对象被销毁(调用 dealloc),内存被回收。

  OC对象的内存生命周期由引用计数控制。每个对象的内存头部(objc_object结构体)都存储了一个isa指针,以及一个隐藏的引用计数字段(retainCount)。当引用计数变为0时,对象被销毁(调用dealloc),内存被回收。

引用计数的存储方式

  • 小对象优化:对于小对象(如NSNumber、NSNull等小尺寸对象)

NSNumber:基本数据类型(如 int、float、BOOL等)的对象化包装

NSNull:空值的对象化表示。NSNull是一个特殊的类,用于表示“空值”(类似其他语言的 null或 None)

  • 大对象:对于大对象(如NSObject子类、NSArray等),引用计数存储在全局的SideTable中。SideTable是一个哈希表,通过对象的地址作为键,映射到包含引用计数和弱引用表的条目(side_table_t)。

retain和release的实现原理(MRC手动管理)

  在MRC模式下,retain和release是手动管理对象生命周期的核心方法,直接操作引用计数。

retain(MRC手动管理)

retain源码

  retain的作用是增加对象的引用计数,确保对象在被持有期间不会被释放。关于retain这部分的源码,在objc4_906_main中如下:

inline id 
objc_object::retain()
{//确保对象不是小对象,如果是,就直接返回自身,因为小对象的值直接编码在指针地址中,无需堆分配,不用引用计数ASSERT(!isTaggedPointer());return rootRetain(false, RRVariant::FastOrMsgSend);
}

在retain内联的rootRetain函数前面,还有一个其的无参数版本:

ALWAYS_INLINE id 
objc_object::rootRetain()
{return rootRetain(false, RRVariant::Fast);
}

这段无参数的rootRetain方法版本,核心作用是通过内联调用简化代码,并为特定场景(如内部优化路径)提供更高效的调用方式,快速触发对象的引用计数增加功能操作。

内联函数rootRetain源码

进入rootRetain后,源码如下:

ALWAYS_INLINE id
//rootRetain是retain操作的核心实现,负责处理引用计数的原子性增加、多线程安全、内联引用计数与侧表的转录等
//参数:tryRetain为false,表示这是一个普通的保留操作,而非尝试保留(尝试保留通常用于锁机制中,避免阻塞)
//     RRVariant::FastOrMsgSend是一个枚举值,指示当前调用路径是快速路径或模拟objc_msgSend发送retain消息的场景
objc_object::rootRetain(bool tryRetain, objc_object::RRVariant variant)
{//检查对象是否为小对象(如 int、BOOL、小 NSNumber等)。小对象的值直接编码在指针地址中(无需堆分配),无需引用计数管理if (slowpath(isTaggedPointer())) return (id)this;bool sideTableLocked = false;  // 标记侧表是否已锁定(避免多线程竞争)bool transcribeToSideTable = false;  // 标记是否需要将内联计数转录到侧表isa_t oldisa;  // 保存旧的 isa 位域状态isa_t newisa;  //新的 isa 位域状态(待更新)oldisa = LoadExclusive(&isa().bits);  //原子加载当前 isa 位域的原子性(避免其他线程修改导致脏读)if (variant == RRVariant::FastOrMsgSend) { //表示当前调用是“快速路径”或“模拟 objc_msgSend发送 retain消息”(如直接调用 [obj retain])// These checks are only meaningful for objc_retain()// They are here so that we avoid a re-load of the isa.if (slowpath(oldisa.getDecodedClass(false)->hasCustomRR())) { //检查对象的类是否覆盖了自定义的引用计数逻辑(如 retain/release方法);若有,跳过默认逻辑,直接调用自定义实现ClearExclusive(&isa().bits);  // 释放原子加载的锁if (oldisa.getDecodedClass(false)->canCallSwiftRR()) {  //判断是否可调用 Swift 的保留函数(swiftRetain),兼容 Swift 对象的引用计数管理return swiftRetain.load(memory_order_relaxed)((id)this);}return ((id(*)(objc_object *, SEL))objc_msgSend)(this, @selector(retain));}}if (slowpath(!oldisa.nonpointer)) {// a Class is a Class forever, so we can perform this check once// outside of the CAS loop//判断元类,元类是类的类,无需被保留(无实例指向元类),因此直接返回自身if (oldisa.getDecodedClass(false)->isMetaClass()) {ClearExclusive(&isa().bits);return (id)this;}}//编译器宏,指示是否启用内联引用计数(现代 iOS/macOS 系统默认启用):若未启用内联引用计数(如旧版本或特殊配置),引用计数完全存储在侧表(SideTable)中,直接调用侧表的 tryRetain或 retain方法
#if !ISA_HAS_INLINE_RC// No need for a CAS loop in this case; we aren't changing the ISA pointerClearExclusive(&isa().bits);if (tryRetain) return sidetable_tryRetain() ? (id)this : nil;else return sidetable_retain(sideTableLocked);
#elsedo {transcribeToSideTable = false;newisa = oldisa;if (slowpath(!newisa.nonpointer)) {  // 重新检查是否为非指针 isa(可能被其他线程修改)ClearExclusive(&isa().bits);if (tryRetain) return sidetable_tryRetain() ? (id)this : nil;else return sidetable_retain(sideTableLocked);}// don't check newisa.fast_rr; we already called any RR overridesif (slowpath(newisa.isDeallocating())) {  //对象正在释放,禁止保留ClearExclusive(&isa().bits);if (sideTableLocked) {ASSERT(variant == RRVariant::Full);sidetable_unlock();}if (slowpath(tryRetain)) {return nil;} else {return (id)this;}}uintptr_t carry;newisa.bits = addc(newisa.bits, RC_ONE, 0, &carry);  // extra_rc++ 原子增加extra_rcif (slowpath(carry)) {  //内联引用计数溢出(extra_rc超过最大值)// newisa.extra_rc++ overflowedif (variant != RRVariant::Full) {  //非完整变体,直接处理溢出ClearExclusive(&isa().bits);return rootRetain_overflow(tryRetain);}// Leave half of the retain counts inline and // prepare to copy the other half to the side table.//准备转录到侧表:保留一半内联计数,另一半存侧表if (!tryRetain && !sideTableLocked) sidetable_lock();  //锁定侧表(仅非尝试保留时)sideTableLocked = true;transcribeToSideTable = true;newisa.extra_rc = RC_HALF;  //内联部分保留一半newisa.has_sidetable_rc = true; //标记使用侧表}} while (slowpath(!StoreExclusive(&isa().bits, &oldisa.bits, newisa.bits)));  //CAS重新提交isa位域if (variant == RRVariant::Full) {if (slowpath(transcribeToSideTable)) {// Copy the other half of the retain counts to the side table.sidetable_addExtraRC_nolock(RC_HALF);  //将剩余的一半引用计数添加到侧表}if (slowpath(!tryRetain && sideTableLocked)) sidetable_unlock(); //非尝试保留时解锁侧表} else {ASSERT(!transcribeToSideTable);ASSERT(!sideTableLocked);}
#endifreturn (id)this;
}

从上述代码中,我们可以知道objc_object::rootRetain的工作流程以原子性增加对象引用计数为核心,代码执行的流程大致如下:

首先初始化 sideTableLockedtranscribeToSideTable 状态变量,并通过 LoadExclusive 原子加载当前 isa 位域(oldisa)。随后进入 do-while 循环,通过 StoreExclusive 原子提交新 isa 位域(newisa),确保多线程下的原子性操作。循环中检查 newisa 是否为非指针 isa(若为非指针则重置锁并重试),并根据 tryRetain 标志选择调用 sidetable_tryRetain(尝试保留)或 sidetable_retain(强制保留)。若对象正在释放(isDeallocating()),则清除锁并根据条件返回 nil或对象自身。若内联引用计数(extra_rc)溢出(carry 标志为真),则将部分计数转录到侧表(标记 has_sidetable_rc)并调整内联值(保留 RC_HALF)。最终提交 isa 更新后,同步侧表数据(若为完整变体)并解锁,返回对象自身完成引用计数增加。

相关的sidetable_tryRetain()方法

接下来我们再来看涉及到的sidetable_tryRetain()方法的源码:

bool
objc_object::sidetable_tryRetain()
{
#if SUPPORT_NONPOINTER_ISA  //编译器宏,指示是否支持非指针 isa(如小对象优化的 Tagged Pointer)ASSERT(!isa().nonpointer);  //若支持非指针 isa,则当前对象的 isa不能是指针类型(nonpointer标志为 false)。此断言用于确保非指针 isa场景下,侧表操作的正确性(避免对非指针 isa执行侧表逻辑)
#endifSideTable& table = SideTables()[this];//SideTables():全局哈希表,存储每个对象地址对应的 SideTable(通过对象地址哈希映射)。SideTable结构:每个 SideTable包含两个核心结构:refcnts:引用计数映射表(RefcountMap),记录对象被弱引用或临时引用的次数;weak_table_t:弱引用表,存储指向该对象的弱引用指针(如 __weak变量)。// NO SPINLOCK HERE// _objc_rootTryRetain() is called exclusively by _objc_loadWeak(), // which already acquired the lock on our behalf.// fixme can't do this efficiently with os_lock_handoff_s// if (table.slock == 0) {//     _objc_fatal("Do not call -_tryRetain.");// }bool result = true;//原子操作,尝试在 refcnts中插入当前对象的条目。若条目已存在(it.second为 false),则直接获取现有条目;若不存在(it.second为 true),则插入新条目,初始值为 SIDE_TABLE_RC_ONE(表示一次弱引用)auto it = table.refcnts.try_emplace(this, SIDE_TABLE_RC_ONE);auto &refcnt = it.first->second;//根据 try_emplace的结果(it.second)和当前引用计数的状态(refcnt),分三种情况处理:if (it.second) { //情况一:插入新条目// there was no entry} else if (refcnt & SIDE_TABLE_DEALLOCATING) { //情况二:条目已存在且对象正在被释放result = false;} else if (! (refcnt & SIDE_TABLE_RC_PINNED)) { //情况三:条目已存在且对象未被释放refcnt += SIDE_TABLE_RC_ONE;}return result;
}

由此,我们可以看来,objc_object::sidetable_tryRetain() 函数通过侧表(SideTable)尝试为对象添加弱引用,流程如下:

首先断言确保对象为指针类型(非小对象优化场景),获取对象对应的侧表;通过原子操作 try_emplace尝试在侧表的引用计数映射(refcnts)中插入当前对象的条目。若插入成功(新条目),初始化引用计数为 SIDE_TABLE_RC_ONE(一次弱引用);若条目已存在,检查对象状态:若对象正在释放(deallocating 标志),则尝试失败;若未被固定(pinned 标志),则将引用计数加 1。最终返回是否成功尝试保留对象(true或 false)。

retain底层工作流程总结

至此,我们可以知道retain底层的工作流程图大致如下:

+----------------------+
|     objc_retain()    |
+----------------------+|v
+--------------------------+
| 检查对象是否为 nil?        |
+--------------------------+|+-------+-------+|               |是(nil)           否|               |
返回 nil       +-----------------------------+|  调用 obj->retain()         |+-----------------------------+|v+----------------------------------------+| objc_object::retain()                  ||                                        || -> 是否启用 Tagged Pointer?             || -> 是否使用 isa-optimized 引用计数?      |+----------------------------------------+|+------------+--------------+|                           |isa优化计数 YES                      NO+-----------------------------+     +-----------------------------+|  利用 isa 指针中位域计数器     |     |  使用 SideTable 哈希表        ||  直接在 isa 中的引用计数 +1    |     |  sidetable_retain()         |+-----------------------------+     +-----------------------------+|v+----------------------------------+| 加锁 -> SideTable.refcnts[obj]+1 || 解锁                             |+----------------------------------+

release

release的作用是减少对象的引用计数,若计数归零则销毁对象。release的底层逻辑:

  1. 检查对象是否为小对象,若是则直接返回。
  2. 获取 SideTable并减少引用计数。
  3. 若引用计数减至 0:调用 dealloc方法(释放实例变量、关联对象等);释放 SideTable内存(若无其他对象使用)。
release源码

在objc_906_main中,这部分源码如下:

inline void
objc_object::release()
{ASSERT(!isTaggedPointer()); // 断言非小对象rootRelease(true, RRVariant::FastOrMsgSend); // 调用核心释放函数
}inline void
objc_object::release()
{ASSERT(!isTaggedPointer()); // 断言非小对象// 快速路径:无自定义释放逻辑时,直接操作侧表if (fastpath(!ISA()->hasCustomRR())) { // 元类无需释放(无实例指向)if (!ISA()->isMetaClass())sidetable_release(); // 释放侧表中的引用计数return;}// 自定义释放逻辑:通过消息发送调用类的 release 方法((void(*)(objc_object *, SEL))objc_msgSend)(this, @selector(release));
}
内联函数rootRelease源码

这部分源码有很多地方的处理与rootRelease相同,有的不再一一赘述。

ALWAYS_INLINE bool 
objc_object::rootRelease()
{return rootRelease(true, RRVariant::Fast);
}ALWAYS_INLINE bool
objc_object::rootRelease(bool performDealloc, objc_object::RRVariant variant)
{if (slowpath(isTaggedPointer())) return false;bool sideTableLocked = false;  // 标记侧表是否锁定(避免多线程竞争)isa_t newisa, oldisa;  //保存新旧 isa 位域状态oldisa = LoadExclusive(&isa().bits);  // 原子加载当前 isa 位域(多线程安全)if (variant == RRVariant::FastOrMsgSend) { //RRVariant::FastOrMsgSend:表示当前调用是“快速路径”或“模拟 objc_msgSend发送 release消息”(优化调用流程)// These checks are only meaningful for objc_release()// They are here so that we avoid a re-load of the isa.if (slowpath(oldisa.getDecodedClass(false)->hasCustomRR())) { //这里跟rootRetain的判断逻辑一样,兼容OC和swiftClearExclusive(&isa().bits);if (oldisa.getDecodedClass(false)->canCallSwiftRR()) {swiftRelease.load(memory_order_relaxed)((id)this);return true;}((void(*)(objc_object *, SEL))objc_msgSend)(this, @selector(release));return true;}}if (slowpath(!oldisa.nonpointer)) {  //元类检查(跟roorRetain一样)// a Class is a Class forever, so we can perform this check once// outside of the CAS loopif (oldisa.getDecodedClass(false)->isMetaClass()) {ClearExclusive(&isa().bits);return false;}}#if !ISA_HAS_INLINE_RC// Without inline ref counts, we always use sidetablesClearExclusive(&isa().bits);return sidetable_release(sideTableLocked, performDealloc);
#else
retry:do {newisa = oldisa;if (slowpath(!newisa.nonpointer)) {ClearExclusive(&isa().bits);return sidetable_release(sideTableLocked, performDealloc);}if (slowpath(newisa.isDeallocating())) {ClearExclusive(&isa().bits);if (sideTableLocked) {ASSERT(variant == RRVariant::Full);sidetable_unlock();}return false;}// don't check newisa.fast_rr; we already called any RR overridesuintptr_t carry;newisa.bits = subc(newisa.bits, RC_ONE, 0, &carry);  // extra_rc--if (slowpath(carry)) {// don't ClearExclusive()goto underflow;}} while (slowpath(!StoreReleaseExclusive(&isa().bits, &oldisa.bits, newisa.bits)));if (slowpath(newisa.isDeallocating()))goto deallocate;if (variant == RRVariant::Full) {if (slowpath(sideTableLocked)) sidetable_unlock();} else {ASSERT(!sideTableLocked);}return false;//引用计数溢出处理(借位逻辑)underflow:// newisa.extra_rc-- underflowed: borrow from side table or deallocate(借位:从侧表借位或触发释放)// abandon newisa to undo the decrementnewisa = oldisa;  // 回滚到旧状态if (slowpath(newisa.has_sidetable_rc)) {  //侧表有引用计数if (variant != RRVariant::Full) {  //非完整变体,直接处理借位ClearExclusive(&isa().bits);return rootRelease_underflow(performDealloc);}// Transfer retain count from side table to inline storage.if (!sideTableLocked) {ClearExclusive(&isa().bits);sidetable_lock();sideTableLocked = true;// Need to start over to avoid a race against // the nonpointer -> raw pointer transition.oldisa = LoadExclusive(&isa().bits);goto retry;}// Try to remove some retain counts from the side table.(从侧表借位(最多借 RC_HALF))auto borrow = sidetable_subExtraRC_nolock(RC_HALF);bool emptySideTable = borrow.remaining == 0; // we'll clear the side table if no refcounts remain there(侧表是否清空)if (borrow.borrowed > 0) {  //成功借位// Side table retain count decreased.// Try to add them to the inline count.bool didTransitionToDeallocating = false;newisa.extra_rc = borrow.borrowed - 1;  // redo the original decrement too 调整内联计数(借位后减1)newisa.has_sidetable_rc = !emptySideTable;  //更新侧表标记bool stored = StoreReleaseExclusive(&isa().bits, &oldisa.bits, newisa.bits);if (!stored && oldisa.nonpointer) {  //提交失败,重试// Inline update failed.// Try it again right now. This prevents livelock on LL/SC // architectures where the side table access itself may have // dropped the reservation.uintptr_t overflow;newisa.bits =addc(oldisa.bits, RC_ONE * (borrow.borrowed-1), 0, &overflow);newisa.has_sidetable_rc = !emptySideTable;if (!overflow) {stored = StoreReleaseExclusive(&isa().bits, &oldisa.bits, newisa.bits);if (stored) {didTransitionToDeallocating = newisa.isDeallocating();}}}if (!stored) {// Inline update failed.// Put the retains back in the side table.ClearExclusive(&isa().bits);sidetable_addExtraRC_nolock(borrow.borrowed);//重新加载isa并重试oldisa = LoadExclusive(&isa().bits);goto retry;}// Decrement successful after borrowing from side table.(借位成功,返回结果)if (emptySideTable)sidetable_clearExtraRC_nolock();//侧表清空则清除if (!didTransitionToDeallocating) {//未触发释放则解锁侧表if (slowpath(sideTableLocked)) sidetable_unlock();return false;}}else {//侧表无引用计数,触发释放// Side table is empty after all. Fall-through to the dealloc path.(跳转到释放路径)}}
deallocate:// Really deallocate.ASSERT(newisa.isDeallocating());ASSERT(isa().isDeallocating());if (slowpath(sideTableLocked)) sidetable_unlock();//解锁侧表__c11_atomic_thread_fence(__ATOMIC_ACQUIRE);//内存屏障,确保操作前的操作可见if (performDealloc) {this->performDealloc();//执行对象的dealloc方法}return true;//释放成功
#endif // ISA_HAS_INLINE_RC
}

由此可见, release方法的底层工作流程以==原子性减少对象引用计数为核心。首先通过 isTaggedPointer() 检查对象是否为小对象(如 int、小 NSNumber等),若是则直接跳过引用计数管理;接着检查类是否覆盖自定义 release逻辑(hasCustomRR),若有则调用自定义实现(如 Swift 的 swiftRelease 或通过 objc_msgSend 发送 release 消息);若为元类(isMetaClass)则直接返回(元类无实例指向,无需释放);对于普通对象,通过 CAS(Compare-And-Swap)原子操作(LoadExclusive/StoreReleaseExclusive)安全减少内联引用计数(存储于 isa位域的 extra_rc字段);若内联计数溢出(extra_rc 减至负数),则从侧表(SideTable)借位(最多借 RC_HALF)并调整内联计数;最终若引用计数归零且无法借位,标记对象为释放状态(isDeallocating),执行内存屏障确保可见性后调用 dealloc 释放内存。

小结

  retain/release是MRC模式下引用计数的操作入口,我们可以通过它们直接操作引用计数字段。虽然现在xcode启用ARC计数,十分方便,但我们还是有必要了解MRC下相关的手动引用计数管理,这有利于我们更好地掌握引用计数机制。

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

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

相关文章

文件同步神器-rsync命令讲解

rsync 是一个强大的文件同步与传输工具,广泛用于本地或远程服务器之间的高效文件备份、镜像或同步。其核心优势是通过增量传输​(仅传输文件差异部分)和压缩减少数据传输量,同时支持保留文件元数据(如权限、时间戳、所…

Rust: 工具链版本更新

遇到 cargo build --release 错误,比如,当前 Rust 工具链版本(1.78.0)低于依赖项所需的最低版本(部分依赖要求 ≥1.82.0)。以下是系统化的解决方案: 🔧 一、升级 Rust 工具链&#x…

Prompt-to-Prompt| 修改Attention会有“反向传播”或梯度计算?

需要注意的几个问题:额外计算开销:Cross-Attention Control原因:Prompt-to-Prompt的编辑方法需要动态干预交叉注意力(Cross-Attention)层的权重,这会引入额外的计算和显存占用:需要缓存注意力矩…

电商API接口的优势、数据采集方法及功能说明

一、电商API接口的核心优势1. 高效性与准确性数据采集效率:API通过标准化参数(如商品ID、类目)直接获取结构化数据(JSON/XML),无需解析HTML,减少误差。例如,采集1000条商品信息&…

iOS企业签名掉签,iOS企业签名掉签了怎么办?

不能上架到App Store的iOS应用 ,几乎每一个开发者的选择都是通过iOS签名这种内测渠道来完成APP的上架任务,最常用的就是企业签名、超级签名以及TF上架,其中最受欢迎的当属于企业签名了。不过企业签名会出现掉签的现象,那么企业签名…

存储成本深度优化:冷热分层与生命周期管理——从视频平台年省200万实践解析智能存储架构

一、冷热分层:存储成本优化的核心逻辑1.1 数据访问的“二八定律”据行业统计,80%的访问集中在20%的热数据上,而超过90天的历史数据访问频率下降70%以上。某视频平台存储超10PB媒体文件,未分层前年存储成本高达680万元,…

Java设计模式之《备忘录模式》

目录 1. 概念 1.1、定义 1.2、适用场景 2、角色划分 3、实现 1、Originator(发起人) 2、Memento(备忘录) 3、Caretaker(管理者) 4、使用示例 4、优缺点 4.1、优点 4.2、缺点 前言 备忘录模式是…

SpringBoot 多环境配置

在实际项目开发中,不同环境往往有不同的配置需求: 开发环境(dev):本地调试,连接测试数据库;测试环境(test):接口联调,接近真实场景;生…

延凡智慧医院数字孪生平台

延凡智慧医院数字孪生平台是延凡科技依托物联网、数字孪生、AI 算法及边缘计算技术打造的医疗场景全要素数字化解决方案,通过构建医院物理实体与虚拟空间的实时映射,实现医疗资源优化、运营效率提升及患者体验升级。一、平台价值(一&#xff…

谈谈WebAssembly、PWA、Web Workers的作用和场景

WebAssembly、PWA 和 Web Workers 是现代 Web 开发中提升性能、扩展能力的重要技术,各自解决不同场景的问题,以下结合实际使用经验分析:一、WebAssembly(Wasm):高性能代码执行作用:WebAssembly …

嵌入式第十八课!!数据结构篇入门及单向链表

在前几章对C语言的学习中,我们学到了:基本的C语法和简单算法面向过程的编程思想而在数据结构这一篇章,我们将要学习:常用的数据存储结构算法面向对象的编程思想数据结构在正式开始学习之前,我们先来了解一下什么是数据…

win10任务栏出问题了,原来是wincompressbar导致的

问题描述兄弟们客户说自己电脑现在有问题了,任务栏显示的都不对,和之前的都不一样,现在使用起来非常难受,我们来看一下,这到底是什么问题吧!到客户现场,查看发现,客户桌面系统最底下…

FFmpegHandler 功能解析,C语言程序化设计与C++面向对象设计的核心差异

FFmpegHandler 功能解析 本文件记录了关于 FFmpegHandler 类中核心函数工作流程的详细解释。Q: FFmpeg逐帧解码,FFmpegHandler::openVideo 和 FFmpegHandler::readAVFrame 这两个函数都分别做了什么? A: 可以把整个过程想象成“准备播放一部电影”&#…

Codeforces Round 1039 (Div. 2) A-C

A. Recycling Center题目大意 给你n个垃圾袋,每个垃圾袋有一个重量 在每秒钟,你可以选择一个垃圾袋,如果他的重量小于等于c,那么你可以不花费硬币丢掉它 当你丢掉一个垃圾袋后,其他垃圾袋在这一秒重量会翻倍 问最少花费…

【设计模式】 原则

单一职责原则 对于一个类而言,有且仅有一个引起他变化的原因或者说,一个类只负责一个职责 如果一个类承担的职责过多,那么这些职责放在一起耦合度太高了,一个职责的变化可能会影响这个类其他职责的能力。 所以我们在做软件设计的时…

windows11右键菜单新增项增加drawio文件,使用draw.io

目录1.新建空白模板2.建立注册表文件1.新建空白模板 这里我们的模板文件路径为 D:\Software\drawio\template.drawio 2.建立注册表文件 首先新建一个.txt文件,我这里取名为menulize.txt,然后将下面的内容复制到.txt文件中 Windows Registry Editor Ver…

解锁网页魔法:零基础HTML通关秘籍

文章目录**解锁网页魔法:零基础HTML通关秘籍**HTML 基础目标HTML 结构认识 HTML 标签HTML 文件基本结构标签层次结构快速生成代码框架HTML 常见标签注释标签注释的原则标题标签: h1-h6段落标签: p换行标签:br综合案例: 展示博客超链接标签: a表格标签**基…

类似 Pixso 但更侧重「网页 / 软件界面设计」「前后端可视化开发」的工具

从 GoView 的 Demo 功能来看,它主要聚焦于数据可视化大屏的低代码搭建,更侧重数据图表配置和页面布局,没有类似 Pixso 的在线 UI 设计(如矢量绘图、组件样式精细化设计)功能,其核心是通过预设组件快速构建数…

MySQL--组从复制的详解及功能演练

2.MySQL的组从复制 2.1 配置mastesr [rootmysqlaa ~]# vim /etc/my.cnf [mysqld] server-id10 datadir/data/mysql socket/data/mysql/mysql.sock default_authentication_pluginmysql_native_password log-binmysql-bin[rootmysqlaa ~]# /etc/init.d/mysqld restart# 进入数据…

JavaScript将String转为base64 笔记250802

JavaScript将String转为base64 笔记250802 在 JavaScript 中将字符串转换为 Base64 编码有多种方法,每种方法都有其适用场景。下面我将全面介绍这些方法,包括处理 ASCII 字符、Unicode 字符以及性能优化方案。 基础方法:btoa() 基本用法&a…