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

iOS 中的引用计数

iOS 中的引用计数

最近面试,遇到引用计数的问题

并且由引用计数引出来的关联问题,在这说明一下

1、引用计数是什么
通常情况下,某一块地址有多少个指针指向了它,那么这个多少就是引用计数的值。是iOS 使用自动引用计数来管理内存。

2、引用计数的存储
总的来说,引用计数的存储位置可以分为三种情况,其目标是在保证正确性的前提下,最大限度地优化性能和内存使用
引用计数(retain count)的存储位置取决于对象所处的状态,主要有以下三种方式:

**1. ​存储在对象的 isa指针的额外比特位中(优化情况)​​ ​

存储在 isa指针中(非指针型 isa/ Tagged Pointer)

这是苹果进行的最重要的优化之一,目的是为了在多数常见情况下,不产生额外的存储开销。

a) 非指针型 isa

在 64 位系统后,一个指针地址是 64 位(8字节),但实际寻址并不需要全部 64 位。苹果利用了这些多余的比特位来存储信息,包括引用计数。
​原理​:对象的 isa指针不再直接指向类对象的内存地址,而是一个包含了类对象地址和对象状态信息的位域。
​存储内容​:isa结构中的 extra_rc字段(例如 19 个比特位)用于存储额外的引用计数。
当一个对象的引用计数为 1 时(即刚创建时),extra_rc的值为 0。
当有新的强引用持有该对象时,retain操作会尝试先给 extra_rc加 1。
只要 extra_rc没有溢出(即引用计数不太大),所有的引用计数操作都直接在这个 isa指针内完成,速度极快,且没有额外的内存访问。
b) Tagged Pointer(特殊情况)

对于某些小对象(如短字符串 NSString、小数字 NSNumber等),苹果使用了 Tagged Pointer 技术。此时,对象的值直接存储在其指针值中,​它根本不是堆上的一个真正对象。
​特点​:对于 Tagged Pointer,​不存在引用计数的概念。retain和 release操作都是空操作,因为它的“内存管理”就是简单的指针赋值和销毁,效率极高。

2. 存储在对象的 Side Table中(溢出情况)​​
当存储在 isa中的引用计数不够用时,系统会使用 Side Table。
​何时触发​:当对象的引用计数持续增加,导致 isa中的 extra_rc字段被塞满(溢出)时。
​工作原理​:
​一半一半策略​:当 extra_rc快满时,retain操作会将 extra_rc的大约一半值转移到一个全局的 SideTables中。
SideTables是一个哈希表,根据对象的地址可以找到对应的 Side Table。
每个 Side Table中有一个 RefcountMap(引用计数表),它以对象地址为 key,存储其额外的引用计数。
此时,isa中的 extra_rc会保留剩余的一半计数值,并设置一个标志位 has_sidetable_rc为 1,表示此对象有部分引用计数存储在 Side Table中。
​操作​:后续的 retain/release操作会先尝试修改 isa.extra_rc,如果不够,再去操作 Side Table中的值。
​为什么这样设计?​​

这是一种缓存思想。将最常用的、较小的引用计数放在访问速度最快的 isa指针中(相当于 L1 缓存),将不常用的、较大的计数部分放在访问稍慢的 Side Table中(相当于内存)。这保证了在绝大多数情况下(对象的引用数不多)性能最优。

3.两者结合使用(现代运行时的主流方式)
对于一个普通的 Objective-C 对象,其引用计数的存储是分级和混合的:
​创建时​:引用计数为 1,存储在 isa.extra_rc中(值为 0,表示实际计数是 extra_rc + 1)。
​频繁引用时​:retain操作优先增加 isa.extra_rc。
​计数溢出时​:将 isa.extra_rc的一部分转移到 Side Table中,isa只保留一部分。后续操作会同时检查两者。
​释放时​:release操作先减少 isa.extra_rc,如果它为 0 且 Side Table中有值,则再从 Side Table中借一些计数填回 isa.extra_rc。当所有计数归零时,对象被销毁。

3.为什么对象的isa.extra_rc 中会溢出,该怎么理解这种溢出
假设 isa.extra_rc字段的比特位数为 ​8​ 位。这意味着它能存储的最大无符号整数值是 2^8 - 1 = ​255。
根据苹果的优化策略,当 extra_rc快满时(比如达到 255 的一半,即 127 左右),系统会进行“分半”处理,而不是等到完全溢出(255)才处理,以防止溢出错误。
场景:一个被大量强引用的单例对象或管理器对象

假设我们有一个 NetworkManager的单例对象,它在 App 启动时被创建。然后,很多个网络请求模块(比如 200 个 RequestHandler对象)都需要强引用这个管理器来发送请求。

第一步:对象创建
​操作​:NetworkManager *manager = [[NetworkManager alloc] init];
​引用计数​:1
​存储方式​:因为是初始状态,引用计数为 1。在优化实现中,isa.extra_rc的实际值被设为 ​0,因为真正的引用计数是 isa.extra_rc + 1。这样设计可以多存一个计数。
isa.extra_rc= 0
isa.has_sidetable_rc= 0 (false,表示未使用 Side Table)
第二步:前 127 次 retain(假设没有 release)
​操作​:200 个 RequestHandler对象开始创建,并强引用 manager。我们执行了 127 次 retain操作。
​引用计数变化​:从 1 增加到 1 + 127 = ​128​
​存储方式​:所有的 retain操作都只是简单地增加 isa.extra_rc的值。
isa.extra_rc= 127 (因为实际计数是 127 + 1 = 128)
isa.has_sidetable_rc= 0
​此时状态​:引用计数完全存储在 isa指针中,速度极快。
第三步:第 128 次 retain- 触发溢出处理

这是最关键的一步。当系统发现 extra_rc的值已经比较大(比如达到了阈值 127,即 255 的一半),为了给后续的 retain留出空间,它会主动进行“分半”处理,将一部分计数转移到 Side Table。
​操作​:第 128 个 RequestHandler强引用 manager,触发第 128 次 retain。
​处理流程​:
a. ​准备转移​:系统决定将 isa.extra_rc中的大约一半(比如 128 的一半,64)转移出去。
b. ​操作 Side Table​:
在全局的 SideTables中,根据 manager对象的内存地址找到对应的 Side Table和它的 RefcountMap。
在 RefcountMap中为 manager创建一个条目,并将其引用计数值设置为 ​64。
c. ​更新 isa​:
将 isa.extra_rc的值更新为 128 - 64 - 1 = 63。(解释:原来的 128 次计数,减去移出去的 64,再减去对象本身占用的 1,剩下 63 存在 extra_rc中)。
将 isa.has_sidetable_rc标志位设置为 ​1,告诉运行时:“这个对象的部分引用计数在 Side Table 里,以后操作要注意。”
​最终存储状态(第 128 次 retain 后)​​:
​总引用计数​ = 1 (对象本身) + isa.extra_rc(63) + Side Table(64) = ​128。结果正确。
isa.extra_rc= 63
isa.has_sidetable_rc= 1
Side Table RefcountMap中 key(manager)对应的 value= 64
第四步:后续的 retain操作(第 129 次到第 200 次)

现在对象处于混合存储模式。
​操作​:继续创建 RequestHandler,执行第 129 次到第 200 次 retain(共 72 次)。
​处理流程​:每次 retain,系统会优先尝试增加 isa.extra_rc。
假设 isa.extra_rc从 63 开始增加,它最多能增加到 255。所以这 72 次 retain可以完全由 isa.extra_rc吸收。
​最终存储状态(第 200 次 retain 后)​​:
isa.extra_rc= 63 + 72 = ​135​
isa.has_sidetable_rc= 1
Side Table中的值保持不变,仍然是 ​64​
​总引用计数​ = 1 + 135 + 64 = ​200。结果正确。
相反的过程:release

当 RequestHandler们开始释放时:
前 135 次 release会先减少 isa.extra_rc,从 135 减到 0。这个过程很快。
当 isa.extra_rc减为 0,但 has_sidetable_rc为 1 时,系统知道 Side Table 里还有计数。
随后的 release操作会从 Side Table 中“借回”一部分计数到 isa.extra_rc中,然后再减少它。例如,系统可能会从 Side Table 的 64 中转移 50 到 isa.extra_rc,然后开始减少这 50。
如此循环,直到 Side Table 和 isa.extra_rc中的计数都归零,对象被正确销毁。

http://www.dtcms.com/a/552941.html

相关文章:

  • C++多线程运行整理
  • 【渲染引擎基础】圣杯架构——固定逻辑时长+插值渲染
  • iOS 崩溃日志分析工具全指南,多工具协同构建稳定性分析体系
  • 做网站推广的难点、襄阳地区网站做的好的
  • 从U-Net到U-Net++:图像分割网络的进阶之路
  • 打工人日报#20251031
  • Huggingface的国内镜像
  • 软件测试工程师面试准备
  • Applications Manager 仪表盘:新增功能亮点
  • 怎样做网站表白网站策划与建设阶段的推广
  • 持续更新|第12弹:基于yolo算法识别的物体抓取
  • 使用Requests和正则表达式实现京东投影仪商品数据爬取
  • rabbitmq-k8s下双架构镜像+手动sts部署完全文档(下)
  • 仿RabbitMQ实现消息队列(二)-安装
  • 三网合一营销型全网站wordpress的功能简介
  • 鸿蒙分布式数据服务(DDS)原理与企业同步实战
  • 《pygame中Sprite类实现多帧动画》注-显示静态图片2-2
  • 2025年10月31日Github流行趋势
  • 深入浅出wpf学习总结
  • 建搜索引擎网站做欧洲电商看哪个网站
  • 【Linux网络】实现一个简单的聊天室
  • HTTPS接口国密安全设计-示例
  • 通过nginx+openssl自签名证书部署https应用并解决不安全问题
  • AI提示词:别再把提示词当 “聊天”—— 它是人机协作的 “接口定义”
  • Rust开发之使用panic!处理不可恢复错误
  • 买业务送网站浙江省住房和城乡建设厅网站查询
  • 网站托管服务 优帮云网站专题页面用什么做
  • Redisson 的分布式锁机制幽默笑话理解
  • Qt-HTTPWebSocket
  • MATLABcode|2个雷达二维目标跟踪滤波系统 - EKF实现,匀速运动模型,输入:雷达观测数据(距离、方位角),输出:目标状态估计(位置、速度)