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

【iOS】weak修饰符

前言

  前面我们已经学习了解了sideTable,今天来看看在OC中,sideTable是如何在我们使用weak时工作的。在OC中,weak修饰符是一种用于声明“弱引用”的关键字,其核心特性是不参与对象的引用计数管理,而且当被引用的对象被释放时,weak指针会自动置为nil(避免野指针)。为探究weak的工作原理和底层逻辑,笔者特写此篇来记录对weak的学习。

引用计数与sideTable

  OC的内存管理是基于引用计数的(ARC 下由编译器自动生成引用计数增减代码)。对象的isa指针指向其类信息,而对象的内存分布中通常包含:

  • isa指针:指向对象的所属类或元类。
  • 引用计数相关数据:早期直接存储在对象内存头部,后来为了减少内存碎片,现将引用计数和弱引用信息分离到sideTable中。

sideTable是什么

  sideTable是一个辅助数据结构(本质是哈希表),用于存储与对象关联的元数据,包括引用计数表、弱引用表和其它元数据(如关联对象、关联引用等)

引用计数表(RefcountMap):记录对象的引用计数值(分散存储,避免每个对象都占用额外空间)。

弱引用表(WeakMap):记录所有指向该对象的 weak指针地址(键为对象地址,值为 weak指针的集合)。

没个对象可能共享一个sideTable(通过哈希计算映射),因此sideTable的内存开销备份谈到多个对象上。

weak修饰符的底层实现原理

首先,我们来看看调用weak时的底层调用。

请添加图片描述
请添加图片描述

weak工作流程第二阶段

我们在调用weak的地方打上断点,然后进行汇编代码调试,然后我们能发现,weak调用了一个objc_storeWeak方法:
请添加图片描述

然后我们在obj4-906-main源码中找到了这部分方法的底层实现:

请添加图片描述

这其实是weak的赋值阶段,用于将weak指针的地址注册到目标对象的弱引用表中。

这个函数中的参数含义如下:

  • location:指向 weak变量的指针(即存储 weak指针的内存地址)。例如,有一个 __weak NSObject *obj;,则 &obj就是 location的值。
  • newObj:要赋值给 weak变量的新对象(可能为 nil)。
  • 返回值:返回旧值(即 location原来的对象,若未修改则为 nil)。

返回值中,有三个参数控制 storeWeak函数的行为逻辑:

DoHaveOld:是否处理旧值

若为 true,函数会先检查 location原有的旧值(即之前指向的对象),并从该旧值的弱引用表中移除当前 location地址(避免旧对象释放时错误地清理已失效的 weak指针);

若为 false,则跳过旧值处理(适用于首次赋值或无需清理旧值的场景)。

DoHaveNew:是否处理新值

若为 true,函数会将 newObj的地址注册到其对应的弱引用表中(即把location地址添加到 newObj的弱引用表 referrers数组中);

若为 false,则跳过新值处理(适用于清空 weak指针的场景,如 obj = nil)。

DoCrashIfDeallocating:对象释放时是否崩溃

若为 true,当 newObj正在被释放(deallocating状态)时,函数会触发崩溃(避免向已释放对象注册弱引用);

若为 false,则允许向正在释放的对象注册弱引用(但后续对象释放时会清理该 weak指针)。

storeWeak函数源码:

storeWeak(id *location, objc_object *newObj)
{ASSERT(haveOld  ||  haveNew);if (!haveNew) ASSERT(newObj == nil);Class previouslyInitializedClass = nil;id oldObj;SideTable *oldTable;SideTable *newTable;// Acquire locks for old and new values.// Order by lock address to prevent lock ordering problems. // Retry if the old value changes underneath us.retry:if (haveOld) {oldObj = *location;oldTable = &SideTables()[oldObj];} else {oldTable = nil;}if (haveNew) {newTable = &SideTables()[newObj];} else {newTable = nil;}SideTable::lockTwo<haveOld, haveNew>(oldTable, newTable);if (haveOld  &&  *location != oldObj) {SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable);goto retry;}// Prevent a deadlock between the weak reference machinery// and the +initialize machinery by ensuring that no // weakly-referenced object has an un-+initialized isa.if (haveNew  &&  newObj) {Class cls = newObj->getIsa();if (cls != previouslyInitializedClass  &&  !((objc_class *)cls)->isInitialized()) {SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable);class_initialize(cls, (id)newObj);// If this class is finished with +initialize then we're good.// If this class is still running +initialize on this thread // (i.e. +initialize called storeWeak on an instance of itself)// then we may proceed but it will appear initializing and // not yet initialized to the check above.// Instead set previouslyInitializedClass to recognize it on retry.previouslyInitializedClass = cls;goto retry;}}// Clean up old value, if any.if (haveOld) {weak_unregister_no_lock(&oldTable->weak_table, oldObj, location);}// Assign new value, if any.if (haveNew) {newObj = (objc_object *)weak_register_no_lock(&newTable->weak_table, (id)newObj, location, crashIfDeallocating ? CrashIfDeallocating : ReturnNilIfDeallocating);// weak_register_no_lock returns nil if weak store should be rejected// Set is-weakly-referenced bit in refcount table.if (!_objc_isTaggedPointerOrNil(newObj)) {newObj->setWeaklyReferenced_nolock();}// Do not set *location anywhere else. That would introduce a race.*location = (id)newObj;}else {// No new value. The storage is not changed.}SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable);// This must be called without the locks held, as it can invoke// arbitrary code. In particular, even if _setWeaklyReferenced// is not implemented, resolveInstanceMethod: may be, and may// call back into the weak reference machinery.callSetWeaklyReferenced((id)newObj);return (id)newObj;
}

weak工作流程第一阶段

刚刚说objc_storeWeak方法是weak的赋值和持有阶段,这是调用weak的第二阶段,在这之前还有一个声明阶段即初始化阶段。

初始化时,runtime会调用objc_initWeak函数,初始化一个新的weak指针指向对象的地址。

源码如下:

请添加图片描述

weak工作流程第三阶段

weak工作流程的第三阶段就是对象释放阶段(清楚弱引用表并置nil):

请添加图片描述

objc_object::clearDeallocating()函数,它是一个内联函数,内部直接调用了sidetable_clearDeallocating()。这说明clearDeallocating()是对外暴露的接口,而sidetable_clearDeallocating()是具体的实现细节,负责实际的清理操作。

请添加图片描述

这个函数的主要操作是处理SideTable中的弱引用表和相关元数据。步骤包括:

  1. 获取SideTable:通过SideTables()[this]获取当前对象对应的SideTable实例。
  2. 加锁:使用table.lock()确保线程安全,避免多线程同时修改SideTable导致数据竞争。
  3. 查找引用计数项:在table.refcnts(引用计数表)中查找当前对象的迭代器it。
  4. 检查弱引用标记:如果找到迭代器且其值包含SIDE_TABLE_WEAKLY_REFERENCED标志(表示该对象有弱引用需要处理),则调用weak_clear_no_lock函数清理弱引用表。
  5. 清理引用计数项:从refcnts中删除当前对象的条目。
  6. 解锁:释放SideTable的锁,确保其他线程可以继续操作。

weak的本质

  1. 运行时维护的弱引用跟踪机制

weak的本质是运行时通过SideTable动态跟踪对象与weak指针的关联关系。其核心特性(不参与引用计数、自动置nil)均由以下机制支撑:

  • 不参与引用计数:weak赋值时不调用retain,对象释放时不依赖weak指针的计数。
  • 自动置nil:通过SideTable中的弱引用表,在对象释放时主动遍历并清理所有关联的weak指针地址。
  1. 弱引用表的集中管理

Weak(即__weak修饰的指针)的本质是运行时在SideTable中维护的一张弱引用表(weak_table_t)。该表存储了所有指向当前对象的weak指针地址(referrers数组),是对象释放时定位并清理weak指针的核心依据。

weak置nil

weak指针置nil的关键操作发生在对象释放阶段,由sidetable_clearDeallocating函数触发:

  1. 对象调用dealloc后,执行objc_object::clearDeallocating(内联函数),调用sidetable_clearDeallocating
  2. sidetable_clearDeallocating获取对象的SideTable并加锁,检查引用计数映射(refcnts)中是否存在SIDE_TABLE_WEAKLY_REFERENCED标记(表示被weak引用过)。
  3. 若存在,调用weak_clear_no_lock函数,遍历该对象的弱引用表(weak_table_t.referrers),将每个weak指针的地址(entry->referrers)处的值置为nil(本质是修改内存为0)。
  4. 清理完成后,删除引用计数映射项并解锁SideTable,完成weak指针的置nil操作。

总结

  weak的实现以SideTable和弱引用表为核心,通过运行时动态跟踪对象与weak指针的关联关系,在对象释放时主动清理所有weak指针并置nil,既避免了循环引用导致的内存泄漏,又保证了内存访问的安全性,其本质是运行时维护的弱引用跟踪机制。

http://www.lryc.cn/news/604476.html

相关文章:

  • 磁盘io查看命令iostat与网络连接查看命令netstat
  • [Qt]QString 与Sqlite3 字符串互动[汉字不乱码]
  • iOS电池寿命与App能耗监测实战 构建完整性能监控系统
  • 常见CMS获取webshell的方法-靶场练习
  • 2025年自动化工程与计算机网络国际会议(ICAECN 2025)
  • C++菱形虚拟继承:解开钻石继承的魔咒
  • 3D空间中的变换矩阵
  • 应用药品 GMP 证书识别技术,实现证书信息的自动化、精准化提取与核验
  • Jupyter Notebook安装使用
  • React 开发中遇见的低级错误
  • 防止飞书重复回调通知分布式锁
  • 从单体到分布式:解锁架构进化密码
  • 基于定制开发开源AI智能名片S2B2C商城小程序的B站私域流量引流策略研究
  • day25——HTML CSS 前端开发
  • eBPF 赋能云原生: WizTelemetry 无侵入网络可观测实践
  • 一款基于 ReactNative 最新发布的`Android/iOS` 新架构文档预览开源库
  • 从训练到推理:Intel Extension for PyTorch混合精度优化完整指南
  • Visual Studio Code 使用指南 (2025年版)
  • 记录Linux下ping外网失败的问题
  • 看涨虚值期权卖方亏损风险有多大?
  • Linux 系统进程管理与计划任务详解
  • 171页|数字经济时代的新思考:如何进行数字化转型和成为数据驱动的企业
  • jenkins连接docker失败【还是没解决】
  • [SKE]Python gmssl库的C绑定
  • OpenBayes 一周速览丨Self Forcing 实现亚秒级延迟实时流视频生成;边缘AI新秀,LFM2-1.2B采用创新性架构超越传统模型
  • windows11环境配置psbody_mesh库编译安装详细教程
  • MySQL设置为严格模式
  • Kubernetes 中 ConfigMap 与 Secret 的深度解析
  • [leetcode] 反转字符串中的单词
  • Syzkaller实战教程2:运行环境配置+实例运行