前言

  前面我们已经学习了解了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.pswp.cn/diannao/93556.shtml
繁体地址,请注明出处:http://hk.pswp.cn/diannao/93556.shtml
英文地址,请注明出处:http://en.pswp.cn/diannao/93556.shtml

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

相关文章

【JVM篇10】:三种垃圾回收算法对比详解

文章目录1. 标记-清除算法2. 复制算法3. 标记-整理算法总结与面试要点在通过 可达性分析等算法识别出所有存活对象和垃圾对象后&#xff0c;垃圾收集器&#xff08;GC&#xff1a;Garbage Collector&#xff09;就需要执行回收操作来释放垃圾对象所占用的内存。以下是三种最基础…

JXD进步25.7.30

1.为啥是update&#xff0c;因为你if判断有问题。或者是你上来就给id赋值了。2. 这个是清空network历史3.断点位置打在这里&#xff1a;打在上面它进不来4.

Flutter开发实战之网络请求与数据处理

第6章:网络请求与数据处理 “数据是应用的血液,网络是连接世界的桥梁。” 在移动应用开发中,与服务器进行数据交互是必不可少的功能。无论是获取用户信息、提交表单数据,还是上传图片、下载文件,都离不开网络请求。本章将带你深入掌握Flutter中的网络编程技巧。 6.1 网络…

快速分页实现热点功能-索引和order by

需求:分页求出进三天的发布视频的权重热度 权重 / 衰减时间 衰减时间 当前时间 - 视频发布时间 小根堆来实现这个公式可以很好的利用半衰期来进行解决难点:如果一次性加载太多到springBoot服务器里面会造成堆内存占用过多&#xff0c;分页又有可能造成深分页问题&#xff0c;…

HAProxy(高可用性代理)

1 HAProxy 简介 HAProxy&#xff08; High Availability Proxy&#xff09;是一个高性能的负载均衡器和代理服务器&#xff0c;为基于 TCP 和 HTTP 的应用程序提供高可用性、负载平衡和代理&#xff0c;广泛应用于提高 web 应用程序的性能和可靠性。它支持多种协议&#xff0c…

Vulnhub靶场:ica1

一、信息收集nmap扫描一下IP。&#xff08;扫不出来的可以看一下前面几篇找ip的步骤&#xff09;下面给了框架的版本是9.2的&#xff0c;我们去kali里搜一下有没有已经公开的漏洞。searchsploit qdPM 9.2 locate 50176.txt more /usr/share/exploitdb/exploits/php/webapps/50…

【Dv3admin】ORM数据库无法查询的问题

Django 运行过程中&#xff0c;数据库连接的健康状态直接影响应用的稳定性和数据访问准确性。长时间空闲的数据库连接经常因外部机制被回收&#xff0c;进而引发数据查询异常和返回无效结果。 本文围绕 Django 中数据库连接长时间空闲导致的连接失效问题&#xff0c;介绍相关的…

使用 Flownex 对机械呼吸机进行建模

当患者无法独立呼吸时&#xff0c;机械呼吸机通过气管插管将富氧空气输送到患者的肺部。肺是敏感而复杂的器官&#xff0c;因此在无法忍受的压力和体积范围内提供空气&#xff0c;根据每分钟所需的呼吸次数计时&#xff0c;并适当加湿和加热。机械呼吸机的精确建模对于其安全有…

力扣刷题日常(7-8)

力扣刷题日常(7-8) 第7题: 整数反转(难度: 中等) 原题: 给你一个 32 位的有符号整数 x ,返回将 x 中的数字部分反转后的结果. 如果反转后整数超过 32 位的有符号整数的范围 [−231, 231 − 1] ,就返回 0. 假设环境不允许存储 64 位整数&#xff08;有符号或无符号&#xff09;.…

串口接收数据包(协议带帧头帧尾)的编程实现方法:1、数据包格式定义结构体2、使用队列进行数据接收、校验解包

这种带帧头帧尾的数据包处理流程可以简单概括为 “识别边界→提取有效数据→验证完整性” 三个核心步骤&#xff0c;具体操作如下&#xff1a;1. 数据包格式定义&#xff08;先约定规则&#xff09;首先明确一个 “合格数据包” 的结构&#xff0c;比如&#xff1a; 帧头&#…

JSON 对象封装教程

JSON 对象封装方法在 Java 中封装 JSON 对象通常使用第三方库&#xff0c;如 org.json、Gson 或 Jackson。以下是几种常见的方法&#xff1a;使用 org.json 库添加 Maven 依赖&#xff1a;<dependency><groupId>org.json</groupId><artifactId>json<…

【WRF-Chem】EDGAR 排放数据处理:分部门合并转化为二进制(Python全代码)

目录 process.py process_biofl.py process_fossil.py process_micro.py process_sector.py 参考 process.py 读取 EDGAR 排放数据库中 2000 至 2023 年间不同行业的甲烷(CH₄)排放数据,进行合并处理,并将总排放以二进制格式保存到文件中。 导入必要的库 import numpy as n…

【学习过程记录】【czsc】1、安装

文章目录 背景 安装 安装python 安装czsc 功能测试 附录 奇葩的报错 背景 详见: https://github.com/waditu/czsc 安装 安装python !重要!作者强调,python必须是大于等于3.8 为此呢,我也是花了一点时间装了一个python3.13。 安装czsc 关于czsc的安装呢,官方也是给出…

Python批量生成N天前的多word个文件,并根据excel统计数据,修改word模板,合并多个word文件

1&#xff0c;需求 根据word模板文件&#xff0c;生成多个带日期后缀的word文件根据excel-每日告警统计数量&#xff0c;逐个修改当日的文档2&#xff0c;实现 shell脚本&#xff1a;根据word模板文件&#xff0c;生成多个带日期后缀的word文件 #!/bin/bash # 生成近一年日期 …

基于uni-app的血糖血压刻度滑动控件

想要做一个基于uni-app的血糖血压刻度滑动控件&#xff0c;hbuilder市场没有好的&#xff0c;参照别人的写了一个。如图&#xff1a;源码&#xff0c;自己放入components里面。<!-- 刻度滑动选择 --> <template><view><view class"slide-title"…

C语言(02)——标准库函数大全(持续更新)

想要了解更多的C语言知识&#xff0c;可以订阅下面的专栏&#xff0c;里面也有很多品质好文&#xff1a; 打怪升级之路——C语言之路_ankleless的博客-CSDN博客 还在持续更新中&#xff0c;以下是学习过程中遇到的一些库函数&#xff08;排序不分先后&#xff09;&#xff1a…

永磁同步电机无速度算法--静态补偿电压模型Harnefors观测器

一、原理介绍本文基于Harnefors教授提出的静态补偿电压模型&#xff0c;可以实现带载零速启动、正反转切换等功能&#xff0c;原理清晰&#xff0c;实现简便。二、仿真模型在MATLAB/simulink里面验证所提算法&#xff0c;搭建仿真。采用和实验中一致的控制周期1e-4&#xff0c;…

[SKE]Python gmssl库的C绑定

Python gmssl库的C绑定 摘要:本文展示gmssl库的C绑定,并给出完整代码。将参考模型从Python脚本迁移到纯C代码中使用gmssl库(TongSuo项目,支持国密算法如SM4,同时兼容AES、DES、3DES、RSA等)。这样,UVM(SystemVerilog)可以通过DPI-C直接调用C函数,而无需嵌入Py…

4.方法的使用

方法是指一段具有独立功能的代码块&#xff0c;只有被调用时才会执行方法的主要作用体现在&#xff1a;代码组织&#xff1a;将原本挤在一起的臃肿代码按照功能进行分类管理例如&#xff1a;将用户注册的验证逻辑、数据库操作、结果返回等分离成不同方法提高复用性&#xff1a;…

day21-Excel文件解析

目录 1. 概述 2. Apache POI 3. XSSF解析Excel文件 3.1. 添加Jar包依赖 3.2. Workbook&#xff08;Excel文件&#xff09; 3.2.2. 加载&#xff08;解析&#xff09;Excel文件 3.3. Sheet &#xff08;工作簿&#xff09; 3.3.1. 创建工作簿 3.3.2. 获取工作簿 3.3.3.…