Objective-C 关联对象 Associated Objects 作用与实现原理

objc4 runtime Associated Objects objc-references.mm 关联对象 Associated Objects 作用与实现原理 关联对象让调用方在不改类布局、不新增 ivar 的情况下,把一组 key - value 挂到任意 Objective-C 对象上。 objc4 的实现不是把字段塞进对象内存,而是用一张运行时全局表保存“对象地址、key、关联值和内存管理策略”的映射。 入口 APIobjc_setAssociatedObject objc_getAssociatedObject 核心存储AssociationsHashMap ObjectAssociationMap 值封装ObjcAssociation policy + value 清理时机objc_destructInstance objc_disposeClassPair 目录 作用与使用边界 公开 API 与 policy 核心结构与字段 set / get / remove 流程 对象销毁与特殊关联 禁止关联对象的场景 测试覆盖点 作用与使用边界 关联对象主要服务于分类、框架扩展、运行时补充状态等场景:调用方只有对象指针和一个稳定 key,就能为对象保存额外值。 这避免了修改类的 ivar 布局,也让分类可以模拟“存储属性”。 适合做什么 给现有类或分类补充少量状态。 把辅助对象、回调 block、缓存值挂到宿主对象。 用唯一地址作为 key,避免和其他调用方冲突。 不适合做什么 高频读写的核心数据结构。 需要类型系统和对象布局明确表达的状态。 清空他人关联值,尤其是随意调用 objc_removeAssociatedObjects。 运行时代价 每次访问都需要全局关联表查询。 set / get / remove 通过 AssociationsManagerLock 串行保护表。 对象析构时可能需要额外释放所有关联值。 **关键结论:**关联对象是“外置 side table”,不是对象内的 ivar。对象只保存一个“可能有 associated objects”的快速标记,真正的 key/value 在全局表里。 ...

June 1, 2026

objc4 Autorelease 一页讲透

objc4 runtime guide Autorelease 的作用和实现原理 一句话:autorelease 是“延迟一次 release”的机制。对象先被登记到当前线程的自动释放池中, 等池子 pop 时再统一发送 release,从而让返回值和临时对象可以跨过当前语句或当前函数继续存活一小段时间。 核心文件:runtime/NSObject.mm 数据结构:AutoreleasePoolPage 线程隔离:TLS hot page 边界标记:POOL_BOUNDARY 先建立正确心智模型 autorelease pool 不是一堆对象的容器类。 在 objc4 里,它更像每个线程私有的一条栈。栈里放两类指针: 普通对象指针,以及表示池边界的 POOL_BOUNDARY。每次 push 池,就压入一个边界并返回这个边界地址作为 token。 每次 autorelease 对象,就把对象指针压到 hot page 的 next 位置。pop 时拿 token 找到边界, 把边界之上的对象按后进先出的顺序逐个 objc_release。 它解决什么问题 **返回 +0 对象:**被调用方可以返回一个“当前调用者可用,但不归调用者拥有”的对象。 **批量释放临时对象:**循环、事件回调、线程入口可以用池边界控制临时对象峰值。 兼容手动引用计数:retain 表示拿所有权,release 表示交回所有权,autorelease 表示稍后自动交回。 **配合 ARC 优化:**运行时和编译器会尝试消除返回值中的多余 retain/autorelease。 一张图看懂栈变化 假设外层池中 autorelease 了 A,又进入内层池 autorelease 了 B、C。内层 pop 只释放 B、C,不影响外层 A。 push 外层 ...

June 1, 2026

Objective-C weak 指针作用与实现原理

objc4 runtime zeroing weak SideTable weak_table_t Objective-C weak 指针:作用与实现原理 __weak 的核心价值是“引用对象但不拥有对象”:它不会增加引用计数;当对象开始销毁时,runtime 会把所有指向它的 weak 槽位自动写成 nil,避免野指针。这一页按 objc4 源码中的真实路径解释它如何登记、读取、搬迁和清零。 阅读顺序 weak 解决什么问题 运行时数据模型 核心类型、字段、方法 关键操作流程 带注释源码片段 一遍记住 1. weak 的作用 不拥有对象__weak id x = obj; 不会 retain obj,所以不会延长对象生命周期。 自动归零对象销毁时,所有保存该对象地址的 weak 变量会被 runtime 写成 nil。 读时短暂保活读取 weak 时,runtime 会先尝试 retain,再 autorelease,保证表达式使用期间对象不被并发释放。 拒绝无效目标目标正在 dealloc 或类不支持 weak 时,形成 weak 引用会失败或崩溃,取决于入口函数。 **一句话模型:**strong 指针管“拥有关系”,weak 指针管“可观察关系”。delegate、反向引用、缓存回调对象通常适合 weak,因为它们不应该阻止真实所有者释放对象。 2. 运行时数据模型 weak 表不是全局一把大表直接裸用,而是挂在分片的 SideTable 上。runtime 用对象地址选择某个 SideTable,在该表内维护引用计数辅助信息和 weak 表,并用同一把锁保护它们。 ...

June 1, 2026

objc4:isa_t、nonpointer isa、引用计数与 SideTable

isa_t、nonpointer isa、引用计数与 SideTable 本文基于 objc4 仓库中的 runtime 源码和相关测试,解释 Objective-C 对象头里的 isa_t 如何同时承载类信息、引用计数、弱引用/关联对象标记,以及这些信息何时转移到 SideTable。 作用 实现原理与位布局 核心结构和关键字段方法 关键流程 测试如何约束行为 1. 作用 传统 Objective-C 对象的第一个机器字是 isa,直接保存类指针。objc4 在支持 SUPPORT_NONPOINTER_ISA 的平台上把这个机器字扩展为 isa_t: 低位和高位保存状态,中间区域保存类信息。这样一次对象头访问就能拿到类、内联引用计数、 是否有弱引用、是否有关联对象、是否可能有 C++ 析构等信息。 为什么要 nonpointer isa 对象绝大多数时候引用计数很小,把计数放进 isa 可让 retain/release 走原子 CAS 快路径,避免进入全局散列表和锁。 为什么仍需要 SideTable isa 空间有限。引用计数溢出、raw isa 对象、弱引用表,以及 deallocating 状态的兼容存储,都需要 SideTable。 raw isa 的存在原因 类对象始终使用 raw pointer isa;某些类通过 instancesRequireRawIsa() 或 运行时配置禁用 nonpointer isa;测试 rawisa.m 还覆盖了 __DATA,__objc_rawisa 段禁用 nonpointer isa 的场景。 ...

June 1, 2026

Tagged Pointer 对象实现讲解

objc4 runtime internals Tagged Pointer 对象 Tagged Pointer 是一种“指针即对象”的表示法:对象没有堆内存,类信息和 payload 直接编码在指针位中。objc4 对它提供了创建、取 tag、取 payload、取类、消息发送、禁用和混淆等完整路径。 目录 作用 实现原理 核心宏、结构、函数 关键流程 测试视角 源码依据 作用 减少堆分配 小整数、短字符串、日期、索引路径等小对象可以直接塞进指针。NSNumber 的小整数就是典型例子,test/taggedNSPointers.m 验证了 NSNumber numberWithInt:1234 是 tagged pointer。 保留对象语义 虽然指针不指向对象内存,它仍能接收消息、查询类、作为字典 key/value,并通过 Foundation/CF 桥接。运行时把 tag 映射到真实 Class 后,继续走常规方法缓存查找。 绕开生命周期表 test/taggedPointers.m 验证 retain、release、autorelease、weak store/load 对 tagged pointer 不产生真实引用计数或弱引用表负担。它们本质上是立即值,不需要释放。 **一句话:**Tagged Pointer 把“对象身份”改成“位编码值”。运行时需要做的,是在所有需要对象元数据的地方识别它,并把 tag 翻译成类。 实现原理 objc4 在 64 位平台启用 tagged pointer。runtime/objc-config.h 中 SUPPORT_TAGGED_POINTERS 对 __LP64__ 为 1;非 64 位平台关闭。tag 标记位在大多数平台使用 MSB,macOS x86_64 使用 LSB。arm64 还使用 split tagged pointer 布局。 ...

June 1, 2026

objc4 Protocol 结构、注册与查询

目录 1. Protocol 的作用 2. 核心结构 3. read_images 读取协议 4. remap 与重复协议 5. 查询与 conforms 判断 6. 动态创建与注册 7. 测试体现的行为 8. 总结 1. Protocol 的作用 在 objc4 中,Protocol * 并不是一个只有名字的轻量句柄,而是运行时管理的一类 Objective-C 对象。 它承载协议名、继承的协议列表、必需/可选方法列表、实例/类属性列表等元数据。类、分类和协议自身都可以引用这些对象。 对外 API 位于 runtime/runtime.h 的 “Working with Protocols” 区域,例如 objc_getProtocol、objc_copyProtocolList、protocol_conformsToProtocol、 protocol_getMethodDescription、objc_allocateProtocol 与 objc_registerProtocol。 runtime/Protocol.mm 中的 @implementation Protocol 则把老式 Objective-C 消息转接到这些 C API。 关键心智模型:编译器把协议写进镜像的 __objc_protolist 等段;runtime 在 _read_images 中发现并安装协议; 后续所有查询都尽量通过“协议名 => 胜出的协议对象”来回到唯一的、当前有效的定义。 2. 核心结构 当前实现中的底层结构定义在 runtime/objc-runtime-new.h。公开的 Protocol * 在实现里经常通过 newprotocol(p) 转成 protocol_t * 使用。 // runtime/objc-runtime-new.h,保留关键字段并加注释 typedef uintptr_t protocol_ref_t; // 尚未 remap 的 protocol_t * struct protocol_t : objc_object { const char *mangledName; // 协议的运行时名字;Swift v1 可能是 mangled 名 struct protocol_list_t *protocols; // 该协议继承/组合的其他协议 method_list_t *instanceMethods; // @required 实例方法 method_list_t *classMethods; // @required 类方法 method_list_t *optionalInstanceMethods; // @optional 实例方法 method_list_t *optionalClassMethods; // @optional 类方法 property_list_t *instanceProperties; // required instance properties uint32_t size; // 磁盘结构大小;用于判断尾部字段是否存在 uint32_t flags; // fixed-up、canonical 等运行时标志 // 以下字段不是所有磁盘协议结构都有,访问前必须看 size。 const char **_extendedMethodTypes; // 扩展类型编码 const char *_demangledName; // Swift demangled 名称缓存 property_list_t *_classProperties; // 类属性列表 }; struct protocol_list_t { uintptr_t count; // 历史原因:count 是指针宽度 protocol_ref_t list[0]; // 变长数组;元素可能需要 remap }; 关键字段与方法 mangledName 全局协议表的 key。objc_getProtocol 先按原名查,再查 dyld 预优化表,再尝试 Swift v1 mangled 等价名。 protocols 协议继承关系。protocol_conformsToProtocol 会递归遍历它;protocol_copyProtocolList 只复制直接继承项。 四个方法列表 按 required/optional 与 instance/class 分成四组。protocol_copyMethodDescriptionList 只返回当前协议直接声明的方法,不包含父协议。 size 兼容小协议或旧 ABI 的关键字段。protocolSmall.m 手工构造较小结构,验证访问尾部字段前必须判定字段是否存在。 flags 高位用于 PROTOCOL_FIXED_UP_* 与 PROTOCOL_IS_CANONICAL,标记是否已修正以及共享缓存中的 canonical 定义。 Protocol 类 runtime/Protocol.h 只暴露不可直接使用的 @interface Protocol; Protocol.mm 中 -conformsTo:、-name、-isEqual: 分别调用 protocol_conformsToProtocol、protocol_getName 与 protocol_isEqual。 ...

June 1, 2026

objc4 Selector / SEL 注册、唯一化与方法名查找

Selector / SEL 注册、唯一化与方法名查找 在 objc4 中,SEL 的关键价值不是承载一段复杂对象,而是把方法名字符串 “驻留”为全进程唯一的指针。注册、镜像加载 fixup、方法列表排序、方法查找和消息缓存都建立在这个事实上: 同名 selector 最终拥有同一个地址,因此比较 selector 可以退化为一次指针比较。 作用 统一方法名身份 编译器、Mach-O 镜像、动态注册 API 和运行时内部方法列表都可能产生方法名。selector 唯一化后,同一个方法名只对应一个 SEL。 让查找足够便宜 方法列表和缓存查找不需要反复比较字符串。命中条件基本都是 method.name == sel 或 bucket.sel == _cmd。 支撑 dyld 预优化 共享缓存中的 selector 可以由 dyld 提供内建唯一值;未优化镜像在 _read_images 中被 runtime 修正到唯一值。 对外头文件把 SEL 声明为不透明类型:typedef struct objc_selector *SEL;。 但在当前实现中,runtime 会把 selector 当成方法名 C 字符串指针使用,sel_getName() 直接把 SEL 转回 const char *。 核心结构、函数与字段 SEL 公开为不透明 selector 指针,实际唯一值是某个方法名字符串的地址。来源:runtime/objc.h。 namedSelectors ExplicitInitDenseSet<const char *>,保存非 dyld 内建 selector 的唯一字符串指针。来源:runtime/objc-sel.mm。 selLock 保护 selector 表。sel_registerName() 自行加锁;镜像加载和方法列表 fixup 常在外层持锁后调用 sel_registerNameNoLock()。 _sel_searchBuiltins() 在支持预优化时向 dyld 查询共享缓存内建 selector,优先返回 dyld 已唯一化的值。 sel_registerName() 注册并返回 selector;若已存在则返回既有唯一值;会复制传入字符串。 sel_lookUpByName() 只查找,不创建。未注册返回 NULL,测试 test/sel.m 明确覆盖该行为。 method_t::name 方法条目的 selector 字段。方法列表 fixup 后,这个字段应是唯一化的 SEL。 cache_t / bucket_t 消息缓存以 SEL 为 key,桶保存 SEL + IMP,hash 直接来自 selector 地址。 实现原理 selector interning 的核心是“先按名字查找唯一槽,再把槽里的字符串地址作为 SEL 返回”。 对 dyld 共享缓存内建 selector,runtime 先问 dyld;对普通 selector,runtime 使用 namedSelectors 保存唯一字符串。 ...

June 1, 2026

+initialize 懒初始化与并发状态机

objc4 runtime 懒初始化 并发等待 fork safety +initialize 懒初始化与并发状态机 +initialize 是 Objective-C 运行时在类第一次被真正使用时触发的类级初始化钩子。 objc4 的实现重点不是“调用一个方法”,而是用每个类的递归初始化锁、元类上的状态位、线程本地的初始化列表和 fork 子进程保护规则,保证父类优先、单线程执行、并发可等待、重入不死锁。 目录 作用 实现原理 核心状态字段 入口与触发 并发状态机 关键流程 测试依据 作用 +initialize 的语义是:在某个类或其子类第一次收到会触发初始化的消息前,运行时先给该类发送一次 +initialize。 这让类可以延迟建立全局状态、注册方法、准备缓存或执行只依赖本类的初始化逻辑。 懒触发 objc_getClass() 只查找类,不触发初始化;普通消息发送、部分运行时查询、autorelease 返回值路径和弱引用相关路径可能需要先完成初始化。 父类优先 子类开始初始化前,运行时递归确保父类已经初始化,避免类层次中间被并发线程插入造成死锁。 一次完成 同一类只有一个线程真正发送 +initialize;其他线程要么等待,要么在同一初始化线程重入时直接放行。 实现原理 objc4 把 +initialize 做成一个按需进入的并发状态机:消息查找路径发现目标类未初始化时,先把类 realize,再释放 runtimeLock,进入 initializeNonMetaClass()。 后者用父类递归、每类递归锁、元类状态位和线程本地初始化列表协调所有线程。真正执行用户代码的线程设置 INITIALIZING,发送 +initialize,最后在 @finally 中切换到 INITIALIZED;其他线程通过同一把类锁等待状态稳定。 这个设计的核心约束是单调性:类状态只从“未初始化”前进到“初始化中”,再前进到“已初始化”。即使 +initialize 抛异常,状态机也会完成收尾;即使发生重入,只有当前初始化线程能绕过等待;即使 fork 发生在多线程初始化期间,子进程也只允许 trivial 初始化继续,其他自定义初始化会主动终止。 核心类/结构状态字段 初始化状态存在类对象的元类标志位里。源码注释明确约束:初始时 RW_INITIALIZING 和 RW_INITIALIZED 都未设置;初始化中只设置前者;完成后清除前者并设置后者;两者永不同时为真,且 RW_INITIALIZED 一旦设置就不会清除。 ...

June 1, 2026

Objective-C +load 调度:load_images 与 call_load_methods

objc4 runtime +load 调度:从 load_images 到 call_load_methods 这页解释 objc4 如何在 dyld 映射镜像后发现、排队并调用 Objective-C 的 +load。 重点是类与分类的顺序、父类优先、递归和重入处理,以及 runtimeLock 与 loadMethodLock 的边界。 入口:dyld callback 队列:loadable_classes / loadable_categories 调度:call_load_methods 测试:load*.m 目录 作用 实现原理 核心结构和关键函数 关键流程 带注释代码片段 测试体现的语义 作用 +load 是 Objective-C 运行时在类或分类被装入进程时主动调用的类方法。 它不依赖消息发送触发,也早于普通的 +initialize。objc4 的任务不是简单遍历所有方法并调用, 而是在 dyld 映射镜像、runtime 完成类注册和分类附着后,按照语言语义和装载依赖稳定地调用。 装载时机 dyld 通过 _dyld_objc_register_callbacks 注册 runtime 回调。镜像映射后先走 map_images,随后 load_images 处理该镜像中的非懒加载类和分类。 顺序语义 类 +load 必须父类优先;所有当前可调用的类 +load 先于分类 +load;分类必须等宿主类已经完成自己的 +load。 重入安全 +load 内部可能 dlopen 新镜像,再次进入 load_images。objc4 让内层调用只排队,真正调用由最外层 call_load_methods 收尾。 实现原理 objc4 将 +load 分成“发现”和“调用”两阶段。发现阶段需要持有 runtimeLock, 因为它要读取和实现类、解析分类、查找元类方法列表。调用阶段释放 runtimeLock, 只持有递归的 loadMethodLock,因为用户代码可能执行任意 Objective-C 行为并重新映射镜像。 ...

June 1, 2026

Objective-C Category 装载与附加机制

objc4 runtime Category 装载与方法、协议、属性附加 Category 的本质不是“修改类结构体”,而是在镜像加载和类实现化时,把分类携带的方法列表、协议列表、属性列表合并到目标类或元类的可变列表视图中。objc4 同时要处理未实现类、stub class、dyld 预附加列表、方法缓存失效和 +load 顺序。 runtime/objc-runtime-new.mm runtime/objc-runtime-new.h runtime/objc-loadmethod.mm test/category.m 等测试 目录 作用 实现原理总览 核心结构和字段 关键流程 带注释核心代码 测试揭示的行为 作用 扩展类行为 分类把实例方法挂到类,把类方法挂到元类,让现有类获得新 selector 或覆盖既有 selector。 扩展反射信息 分类的协议和属性参与 class_copyProtocolList、class_getProperty 等运行时查询。 保持加载顺序 后加载的分类应优先被方法查找看到,所以列表附加采用“前插”。 支持动态镜像 bundle、共享缓存、stub class 和未实现类都能在不同时间点接收分类。 关键结论:分类附加不是把方法逐个复制进类定义,而是把 method_list_t、protocol_list_t、property_list_t 这类“列表”挂到 class_rw_ext_t 的数组视图前端。 实现原理总览 Mach-O 镜像 __objc_catlist / __objc_catlist2 中保存 category_t *。runtime 通过 header_info 知道分类来自哪个镜像。 → 分类分流 目标类未实现则暂存到 unattachedCategories;目标类已实现则立即 attachCategories。 → 列表前插 attachLists 把新增列表放在旧列表前面,方法查找和反射会先看到分类列表。 依据:load_categories_nolock 分流逻辑见 runtime/objc-runtime-new.mm:3585-3717;列表前插见 runtime/objc-runtime-new.h:2021-2105。 ...

June 1, 2026