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

【iOS】SideTable

文章目录

    • 前言
    • 1️⃣Side Table 的核心作用:扩展对象元数据存储
      • 1.1 传统对象的内存限制
      • 1.2 Side Table 的定位:集中式元数据仓库
    • 2️⃣Side Table 的底层结构与关联
      • 2.1 Side Table 与 isa 指针的关系
      • 2.2 Side Table 的存储结构
      • 2.3 SideTable 的工作流程
    • 3️⃣SideTable 的典型应用场景
      • 3.1 弱引用(`__weak`)的实现
      • 3.2 关联对象(Associated Objects)
      • 3.3 KVO(键值观察)的实现
      • 3.4 引用计数的优化存储
    • 4️⃣SideTable特点
    • 总结

前言

在 iOS/macOS 的内存管理中,Side Table(边表) 是苹果为优化对象元数据存储而设计的 辅助数据结构。它与对象的 isa 指针紧密关联,用于存储对象的额外信息(如引用计数、弱引用指针、关联对象等),解决了传统对象内存布局无法高效扩展的问题。它的设计与使用是OC运行时实现弱引用的基础,使得ARC能够正确地处理弱引用的生命周期。

1️⃣Side Table 的核心作用:扩展对象元数据存储

1.1 传统对象的内存限制

OC 对象的内存布局以 isa 指针为核心(指向类对象),但仅靠 isa 无法存储所有元数据:

  • 普通对象的 isa 指针仅指向类对象,无法直接存储引用计数、弱引用等动态信息。
  • 若为每个对象单独分配元数据内存,会导致内存碎片和性能下降。

1.2 Side Table 的定位:集中式元数据仓库

Side Table 是 全局共享的哈希表(或数组),为需要额外元数据的对象提供统一的存储空间。它的核心作用是:

  • 存储对象的扩展元数据(如引用计数、弱引用指针、关联对象键值对)。
  • 减少内存碎片:多个对象共享同一 Side Table 的存储空间,避免为每个对象单独分配内存。

2️⃣Side Table 的底层结构与关联

2.1 Side Table 与 isa 指针的关系

OC 对象的 isa 指针(Class 类型)在 64 位系统中被扩展为 uintptr_t 类型(无符号长整型),通过 位域(Bit Field) 存储额外信息:

  • 低 48 位:指向类对象或 Side Table 的地址(具体取决于对象类型)。
  • 高 16 位:存储标记位(如是否为 weak 对象、是否需要 Side Table 等)。
/*_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)*/

在上述isa的结构体代码中,extra_rchas_sidetable_rc,这两者共同记录引用计数器。

关键逻辑
当对象需要存储额外元数据时(如弱引用),isa 指针的高位会被标记为“需要 Side Table”,并通过低 48 位指向对应的 Side Table 条目。

2.2 Side Table 的存储结构

Side Table 的底层实现是一个 全局的哈希表(或数组),每个条目对应一个对象的内存地址,存储其扩展元数据。不同平台(iOS/macOS)的实现略有差异,但核心结构相似:

字段描述
refcount引用计数(用于 ARC 管理)。
weak_refs弱引用指针数组(存储指向该对象的弱引用变量地址)。
associated_objects关联对象键值对(存储通过 objc_setAssociatedObject 设置的关联数据)。
flags标记位(如是否被标记为待释放、是否为类对象等)。

在obj4-906中,sideTable部分的结构如下:

struct SideTable {spinlock_t slock;  //自旋锁RefcountMap refcnts;  //引用计数映射表weak_table_t weak_table;  //弱引用表SideTable() {memset(&weak_table, 0, sizeof(weak_table));} //SideTable():构造函数,使用memeset将weak_table初始化为0,确保弱引用表的初始状态安全。~SideTable() {_objc_fatal("Do not delete SideTable.");} //~SideTable():析构函数,调用 _objc_fatal("Do not delete SideTable.")防止手动释放。SideTable的生命周期由 Objective-C 运行时管理(如对象销毁时自动释放),禁止用户主动删除。void lock() { slock.lock(); }void unlock() { slock.unlock(); }//直接操作自旋锁 slock,用于保护对 refcnts和 weak_table的修改。void reset() { slock.reset(); }  //重置自旋锁状态(具体实现依赖 spinlock_t的底层逻辑,通常用于释放锁或重置为未锁定状态)。// Address-ordered lock discipline for a pair of side tables.template<HaveOld, HaveNew>static void lockTwo(SideTable *lock1, SideTable *lock2);template<HaveOld, HaveNew>static void unlockTwo(SideTable *lock1, SideTable *lock2);
};

slock是一个自旋锁:保证对 SideTable中数据(如引用计数、弱引用表)的线程安全访问。自旋锁适用于临界区较小的场景(引用计数修改通常很快),避免线程阻塞开销。
请添加图片描述
refcnts是引用计数映射表:存储当前对象的引用计数值。RefcountMap本质是一个轻量级的哈希表或键值对结构,键为对象的内存地址(或唯一标识),值为对应的引用计数值。
请添加图片描述

weak_table是弱引用表:存储所有指向当前对象的弱引用指针(__weak修饰的指针)。弱引用表的核心功能是:当对象被释放时,自动将所有弱引用置为 nil,避免野指针。

weak_table_t源码结构如下:

/*** The global weak references table. Stores object ids as keys,* and weak_entry_t structs as their values.*/
struct weak_table_t {weak_entry_t *weak_entries;  //弱引用条目数组:指向 weak_entry_t结构体的指针数组。每个 weak_entry_t对应一个指向当前对象的 __weak指针,存储弱引用的具体信息(如指针地址、对象状态等)size_t    num_entries;  //弱引用条目数量:记录当前 weak_table_t中有效弱引用条目的数量(即 weak_entries数组中实际使用的元素个数)uintptr_t mask;  //哈希掩码:用于计算弱引用的哈希索引,优化哈希表的存储和查找效率//弱引用的哈希值通常通过 hash ^ mask计算(hash是弱引用指针的哈希值),结果对齐到 weak_entries数组的大小。mask的值通常为数组大小减一(如数组大小为 2^n,则 mask = (1 << n) - 1),确保索引在数组范围内uintptr_t max_hash_displacement;  //最大哈希位移
};

关于weak_entry_t,,其源码在obj4_906中是这样的:

#define WEAK_INLINE_COUNT 4
#define REFERRERS_OUT_OF_LINE 2
struct weak_entry_t {DisguisedPtr<objc_object> referent;union {struct {weak_referrer_t *referrers;  // 外联弱引用指针数组(动态分配)uintptr_t        out_of_line_ness : 2;  // 标记为外联存储(固定为 1)uintptr_t        num_refs : PTR_MINUS_2;  // 弱引用数量(减去 2 位用于其他标记)uintptr_t        mask;  // 哈希掩码(用于快速查找)uintptr_t        max_hash_displacement;  // 最大哈希位移(解决哈希冲突)};struct {// out_of_line_ness field is low bits of inline_referrers[1]weak_referrer_t  inline_referrers[WEAK_INLINE_COUNT];};bool out_of_line() {return (out_of_line_ness == REFERRERS_OUT_OF_LINE);}weak_entry_t& operator=(const weak_entry_t& other) {memcpy(this, &other, sizeof(other));return *this;}weak_entry_t(objc_object *newReferent, objc_object **newReferrer): referent(newReferent){inline_referrers[0] = newReferrer;for (int i = 1; i < WEAK_INLINE_COUNT; i++) {inline_referrers[i] = nil;}}
};

2.3 SideTable 的工作流程

由上述源码,我们可以略微推断出sideTable的工作流程:

  1. 对象创建:当对象大小超过小对象阈值时,运行时为其分配 SideTable,并将引用计数初始化为 1(retainCount)。
  2. 引用计数修改:调用 retain/release时,运行时通过对象的 isa指针找到对应的 SideTable,加锁后修改 refcnts中的计数值。
  3. 弱引用注册:当对象被 __weak指针指向时,运行时将其弱引用指针注册到 SideTableweak_table中。
  4. 对象释放:当引用计数减至 0 时,运行时触发 dealloc,遍历 weak_table将所有弱引用置为 nil,然后释放 SideTable关联的资源(如 refcntsweak_table内存)。

3️⃣SideTable 的典型应用场景

3.1 弱引用(__weak)的实现

__weak 修饰的变量不会增加对象的引用计数,但需在对象释放时自动置空。Side Table 是其核心实现载体:

流程

  1. 当对象被 __weak 变量引用时,系统会在 Side Table 中为该对象创建条目。
  2. __weak 变量的值存储为 Side Table 条目的索引(而非直接存储对象地址)。
  3. 对象释放时,通过 Side Table 找到所有指向它的 __weak 变量,并将其置空。

3.2 关联对象(Associated Objects)

通过 objc_setAssociatedObjectobjc_getAssociatedObject 设置的关联对象,其数据实际存储在 Side Table 中:

示例

// 为 NSObject 实例添加关联对象(键为 "com.example.name")
NSString *name = @"张三";
objc_setAssociatedObject(obj, (__bridge const void *)(@"com.example.name"), name, OBJC_ASSOCIATION_RETAIN_NONATOMIC);// 从 Side Table 中读取关联对象
NSString *storedName = objc_getAssociatedObject(obj, (__bridge const void *)(@"com.example.name"));

底层逻辑
关联对象的键(如 @"com.example.name")和值会被存储在 Side Table 的 associated_objects 字段中,键通过哈希映射快速查找。

3.3 KVO(键值观察)的实现

KVO 的核心是 动态生成子类 并重写 setter 方法,但观察者列表(observers)的存储依赖 Side Table:

流程

  1. 当对对象属性添加 KVO 观察时,系统会生成一个该对象的子类(如 NSKVONotifying_Obj)。
  2. 子类的 isa 指针指向 Side Table 中的观察者列表条目。
  3. 属性值变化时,通过 Side Table 找到所有观察者并触发通知。

3.4 引用计数的优化存储

ARC 下,对象的引用计数(retainCount)不再直接存储在对象内存中,而是通过 Side Table 的 refcount 字段统一管理:

  • 引用计数增加(retain)时,更新 Side Table 中的 refcount
  • 引用计数减少(release)时,检查 refcount 是否为 0,若为 0 则触发对象释放。

4️⃣SideTable特点

  • 内存布局:紧凑高效

SideTable 的条目(Entry)在内存中是 连续存储 的,通过哈希表快速查找。每个条目的大小根据存储内容动态调整(如仅存储弱引用时,条目较小;存储关联对象时,条目较大)。

  • 线程安全:锁保护

Side Table 的读写需保证线程安全,苹果通过 自旋锁(Spin Lock)信号量(Semaphore) 实现:修改 Side Table(如添加/删除条目)时加锁;读取 Side Table(如获取弱引用列表)时加锁或使用无锁读取(CAS 操作)。

  • 版本演进:从 __objc_sideTableobjc_sideTable

早期 iOS 版本(如 iOS 9 前)使用 __objc_sideTable 结构体,存储方式为数组;iOS 10 后优化为哈希表(objc_sideTable),提升了查找效率。

总结

Side Table 是 iOS 内存管理的“元数据中心”,通过集中存储对象的扩展信息(引用计数、弱引用、关联对象等),解决了传统对象内存布局的局限性。它的存在让 OC 能够高效支持 ARC、弱引用、KVO 等高级特性,是苹果内存管理优化的关键技术之一。

http://www.xdnf.cn/news/16132.html

相关文章:

  • 【深度解析】从AWS re_Invent 2025看云原生技术发展趋势
  • C语言(20250722)
  • 网络编程---TCP协议
  • 跨越语言壁垒!ZKmall开源商城多语言架构如何支撑电商全球化布局
  • libgmp库(GNU高精度算术库)介绍
  • mac实现sudo命切换node版本
  • netty的编解码器,以及内置的编解码器
  • OpenCV 零基础到项目实战 | DAY 1:图像基础与核心操作
  • LLC协议
  • mysql_innodb_cluster_metadata源数据库
  • Vue3 面试题及详细答案120道(31-45 )
  • Web3面试题
  • 智慧能源合同解决方案
  • 【接口自动化】pytest的基本使用
  • XML高效处理类 - 专为Office文档XML处理优化
  • Aspose.Cells 应用案例:法国能源企业实现能源数据报告Excel自动化
  • Python通关秘籍(五)数据结构——元组
  • Rocky Linux 9 快速安装 Node.js
  • 3.5 模块化编程实践
  • 【数据结构初阶】--栈和队列(二)
  • Python之格式化Conda中生成的requirements.txt
  • 我的第一个开源项目 -- 实时语音识别工具
  • 数据库表介绍
  • 算法提升之字符串回文处理-(manacher)
  • 自编码器表征学习:重构误差与隐空间拓扑结构的深度解析
  • 客户案例 | Jabil 整合 IT 与运营,大规模转型制造流程
  • 《小白学习产品经理》第八章:方法论之马斯洛需求层次理论
  • Java新特性-record
  • 力扣-139.单词拆分
  • js的基本内容:引用、变量、打印、交互、定时器、demo操作