objc4 方法缓存 cache_t / bucket_t 技术讲解

objc4 方法缓存 cache_t / bucket_t 技术讲解 本文基于当前仓库中的 runtime/objc-cache.mm、 runtime/objc-runtime-new.h、runtime/objc-runtime-new.mm 以及 cache flush 相关测试,说明 Objective-C runtime 如何把一次慢速方法查找变成后续的快速 objc_msgSend cache hit。 一、方法缓存的作用 Objective-C 消息发送以 (Class, SEL) 为核心输入。完整方法查找需要处理类实现、父类链、 动态方法解析、转发和分类变更等逻辑,代价高于一次普通函数调用。cache_t 是挂在每个 objc_class 上的 IMP cache,用 SEL 作为 key,缓存最终应该调用的 IMP。 加速热路径命中时 objc_msgSend 不进入 runtime 慢路径,直接由 bucket 中的 IMP 跳转。 缓存继承结果子类未实现某 selector 时,也可以把父类找到的 IMP 缓存在子类 cache 中。 支持动态变更分类加载、添加方法、交换实现等操作会触发 cache flush,避免继续调用旧 IMP。 cache 不是方法列表本身,也不是权威数据源。它是一个可丢弃、可重建的性能结构;扩容时甚至不会搬迁旧条目, 而是让后续消息重新填充热点 selector。 二、核心结构 bucket_t:一个 selector 到 IMP 的槽位 字段 _sel 保存 selector,_imp 保存编码后的 IMP。arm64 上 IMP 在前,其他架构通常 SEL 在前,以贴合汇编 fast path 和指针认证需求。 sel() 以 relaxed atomic 读取 selector。空槽的 selector 为 0。 imp(base, cls) 读取并解码 IMP。arm64e 可使用 ptrauth,部分配置使用 class 指针 XOR,未编码配置则直接返回。 set<Atomic, Encoded>() 写入一个 bucket。写入顺序被精心安排,保证无锁读取者不会看到“新 SEL + 旧 IMP”的错误组合。 cache_t:每个类上的哈希表 _bucketsAndMaybeMask 保存 buckets 指针,有些 64 位配置还把 mask 打包进高位;preoptimized cache 也用低位 marker 复用这个字段。 _mask / 内联 mask bucket 数量始终为 2 的幂,mask = capacity - 1,哈希后用按位与得到起始槽。 _occupied 已占用动态 bucket 数。插入成功后递增;换表时清零。 _flags 保存若干快速路径标记,例如 metaclass、C++ ctor/dtor、默认 alloc 或 RR 等,具体位定义在 objc-runtime-new.h。 关键方法 insert、eraseNolock、destroy、copyCacheNolock、maybeConvertToPreoptimized、preoptFallbackClass。 preopt_cache_t:dyld shared cache 预构建的常量 cache fallback_class_offset 预优化查找未覆盖时继续查找的类,相对当前 class 地址保存。 shift / mask 用于计算预优化 entries 下标。capacity() 返回 mask + 1。 occupied 预优化 cache 中的有效条目数量。 has_inlines 标记是否存在内联 selector;方法列表变更时需要更谨慎地禁用这类 cache。 entries[] 每项保存 selector offset 和 IMP offset,而不是直接保存完整指针。 三、实现原理 动态 cache 是一个开放寻址哈希表。cache_hash(sel, mask) 用 selector 地址和 mask 得到起始槽; 冲突时调用 cache_next 继续探测。不同架构的探测方向不同:部分架构递增并使用 end marker, arm64 递减并通过 mask 回绕。 ...

June 1, 2026

Objective-C 消息发送 objc_msgSend 与慢速查找入口

objc4 runtime dispatch Objective-C 消息发送:objc_msgSend 与慢速查找入口 Objective-C 的方法调用本质上是一次以 receiver 和 SEL 为键的动态派发。 在 arm64 上,objc_msgSend 先用少量汇编完成 nil/tagged pointer 判断和方法缓存查找; 只有缓存未命中时,才进入 C++ 慢路径 lookUpImpOrForward,沿类层级查找、触发动态解析、填充缓存或进入消息转发。 入口:runtime/Messengers.subproj/objc-msg-arm64.s 慢路径:runtime/objc-runtime-new.mm 缓存:runtime/objc-cache.mm 测试:test/msgSend.m 目录 作用 实现原理总览 核心结构与约定 快速路径 慢速查找入口 消息转发 测试观察点 作用 objc_msgSend(id self, SEL _cmd, ...) 是 Objective-C 实例方法和类方法调用的公共派发入口。 编译器把 [obj method:arg] 形式的调用降低为对 objc_msgSend 的调用;真正执行哪个 IMP 由运行时根据接收者的实际类、选择子和当前方法缓存决定。 动态派发 同一个 SEL 可以在不同类上命中不同 IMP,体现多态。 缓存加速 大多数热路径只扫描类的 cache_t,命中后直接尾调用 IMP。 语义兼容 nil 接收者返回零值;tagged pointer 通过标签映射到伪类继续派发。 扩展入口 未找到实现时支持 +resolve... 和完整的 forwarding 机制。 ...

June 1, 2026

objc4 类加载、read_images 与类实现化

objc4 类加载、read_images 与类实现化 本文解释 Objective-C runtime 从 dyld 通知镜像映射,到读取类、处理 future class、realize class、methodize class、附着 category 的主干流程。 重点源码来自 runtime/objc-runtime-new.mm、objc-runtime-new.h、objc-os.mm、objc-class.mm 及相关测试。 作用 核心结构 关键流程 测试视角 一、这条链路的作用 把 Mach-O 中的静态元数据变成运行时可用的类 编译器把类、元类、方法列表、协议列表、属性列表和 ivar 布局写入 Mach-O 的 Objective-C sections。 这些数据起初主要是只读的 class_ro_t。runtime 加载镜像时要登记类名、修正 selector/protocol/class 引用,并在必要时分配可写的 class_rw_t。 延迟成本,同时保证消息发送能看到正确结构 大量类可以保持未 realized 状态,直到非懒加载、+load、消息发送、Swift 桥接或显式 API 需要它们。 realize 时才连接父类和元类、修正 ivar 偏移、初始化 cache、复制运行时标志,并把方法列表整理成可查找的形态。 简化理解:read_images 是“发现并登记”;readClass 是“读一个类声明并处理 future/remap”;realizeClassWithoutSwift 是“把类接入运行时继承图”;methodizeClass 是“准备方法/协议/属性并合并 category”。 二、核心类/结构和关键字段 结构 关键字段 作用 objc_class superclass、cache、bits 类对象本体。bits 初始可指向 class_ro_t,realize 后指向 class_rw_t 并夹带 fast flags。 class_ro_t flags、instanceStart、instanceSize、name、baseMethods、ivars 编译期只读描述。包含类名、方法、协议、属性、ivar 布局和 root/meta/future/realized 等 ABI 标志。 class_rw_t flags、ro_or_rw_ext、firstSubclass、nextSiblingClass 运行时可写状态。保存 realized/initialized/constructing 等状态,并把类接入子类链。 class_rw_ext_t ro、methods、properties、protocols 当类需要扩展列表时分配。category 附着后,方法/属性/协议列表通常进入这里。 method_list_t entsizeAndFlags、count、方法条目 方法列表。加载时会 uniquing selector,必要时排序并标记 fixed-up。 category_t cls、instanceMethods、classMethods、protocols、instanceProperties 分类元数据。目标类已 realized 时立即附着,否则暂存到 unattached categories。 header_info classlist、nlclslist、catlist、selrefs、protocolrefs 一个 Mach-O 镜像的 ObjC 元数据索引。objc-os.mm 中 addHeader 负责创建或取出它。 类数据从 ro 切换到 rw struct objc_class : objc_object { Class superclass; cache_t cache; class_data_bits_t bits; // 未实现时指向 class_ro_t,实现后指向 class_rw_t }; struct class_rw_t { uint32_t flags; // RW_REALIZED、RW_FUTURE、RW_INITIALIZED 等 explicit_atomic<uintptr_t> ro_or_rw_ext; Class firstSubclass; Class nextSiblingClass; }; class_data_bits_t::safe_ro() 允许在并发 realize 场景下安全取出 ro。 setData() 则在 realization 或构造阶段把 objc_class::bits 改成 rw 指针,并设置 FAST_IS_RW_POINTER。 ...

June 1, 2026

CocoaPods 源码导读:架构总览

本系列基于 CocoaPods 1.16.2(2026 年 4 月)源码进行分析。源码仓库由 15 个 Ruby Gem 组成,本文先从整体架构与职责拆分讲起,再以 pod install --repo-update 为主线绘制全景执行图,串起后续两篇专题的切入点。 系列目录: 架构总览(本文) 从命令到依赖求解 从下载到工程集成 一、CocoaPods 不是单一仓库 很多人以为 CocoaPods 就是一个 Ruby 项目,其实官方仓库 CocoaPods/CocoaPods 只是入口,真正的能力被拆成 15 个独立 gem,每个 gem 只做一件事。用 gem dependency cocoapods 会看到这样的依赖拓扑: graph TB subgraph "入口" A["bin/pod(CocoaPods gem)"] end subgraph "命令行框架" B["CLAideCommand/Arg/Option DSL"] end subgraph "领域模型" C["CorePodfile/Podspec/Source/Lockfile"] end subgraph "依赖求解" D["Molinillo回溯式 SAT 求解器"] end subgraph "下载" E["cocoapods-downloaderGit/HTTP/SVN/Hg/SCP"] end subgraph "Xcode 工程读写" F["Xcodeprojpbxproj/xcconfig/workspace"] G["NanaimoASCII plist 解析"] end subgraph "插件与子命令" H["cocoapods-plugins"] I["cocoapods-trunkcocoapods-searchcocoapods-trycocoapods-deintegrate"] end subgraph "辅助" J["cork彩色输出"] K["nap轻量 HTTP 客户端"] end A --> B A --> C A --> D A --> E A --> F F --> G A -.加载.-> H H -.调用.-> I A --> J I --> K 各 gem 的一句话职责: ...

May 2, 2026

CocoaPods 源码导读:从命令到依赖求解

本文是系列第二篇,基于 CocoaPods 1.16.2 源码。我们从 bin/pod 进场,走完 CLAide 的命令分发、Podfile 的 DSL 求值、Analyzer 的七步分析、Resolver + Molinillo 的回溯求解,最后交付 AggregateTarget/PodTarget 给下一篇讲的下载与集成。 上一篇:架构总览 · 下一篇:从下载到工程集成 运行示例贯穿全文:pod install --repo-update 一、bin/pod:16 行的入口 CocoaPods 的可执行文件非常薄,核心逻辑全在 require 'cocoapods' 之后的 Pod::Command.run: # CocoaPods/bin/pod : 24 if $PROGRAM_NAME == __FILE__ && !ENV['COCOAPODS_NO_BUNDLER'] ENV['BUNDLE_GEMFILE'] = File.expand_path('../../Gemfile', __FILE__) require 'rubygems' require 'bundler/setup' $LOAD_PATH.unshift File.expand_path('../../lib', __FILE__) elsif ENV['COCOAPODS_NO_BUNDLER'] require 'rubygems' gem 'cocoapods' end STDOUT.sync = true if ENV['CP_STDOUT_SYNC'] == 'TRUE' require 'cocoapods' if profile_filename = ENV['COCOAPODS_PROFILE'] # 开 ruby-prof 做性能剖析 # ... reporter.new(RubyProf.profile { Pod::Command.run(ARGV) }).print(io) else Pod::Command.run(ARGV) end 这里有两个工程化细节值得留意: ...

May 2, 2026

CocoaPods 源码导读:从下载到工程集成

本文是系列第三篇,基于 CocoaPods 1.16.2 源码。上一篇我们停在 Analyzer 产出 AnalysisResult,本文继续走完 download_dependencies → validate_targets → generate_pods_project → integrate_user_project → write_lockfiles 这五个阶段。 上一篇:从命令到依赖求解 · 首篇:架构总览 一、全景:从 spec 到可编译的工程 先把本文要走的流程重新放一遍,让你带着地图读代码: flowchart TB A[AnalysisResult] --> B[download_dependencies] B -->|per pod| B1[PodSourceDownloader] B1 -->|命中| C1[Downloader::Cache] B1 -->|未命中| C2[cocoapods-downloaderGit/HTTP/SVN/...] C2 --> C3[rsync → Pods/<name>/] B -->|per pod| B2[PodSourceInstaller] B2 --> B3[PodSourcePreparerprepare_command] B2 --> B4[PodDirCleaner按 spec.source_files 裁剪] B --> D[validate_targetsTargetValidator#validate!] D --> E[generate_pods_project] E --> E1[ProjectCacheAnalyzer增量识别要重生成的 target] E --> E2[SinglePodsProjectGenerator#generate!] E2 --> E21[FileReferencesInstaller] E2 --> E22[PodTargetInstaller] E2 --> E23[AggregateTargetInstaller] E2 --> E24[xcconfig / modulemap / umbrella / info.plist / dummy.m] E --> E3[PodsProjectWriter#write!写盘前触发 Podfile post_install] E --> F[integrate_user_project] F --> F1[create_workspace写 .xcworkspace] F --> F2[deintegrate_removed_targets] F --> F3[TargetIntegrator#integrate!xcconfig / frameworks / scripts] F --> G[write_lockfiles] G --> G1[Podfile.lock] G --> G2[Pods/Manifest.lock] G --> H[perform_post_install_actionsplugin post_install hook] 对应的 Installer#install! 片段: ...

May 2, 2026

fishhook 源码导读

fishhook 由 Facebook 于 2013 年开源,用于在运行时动态重绑定(rebind)Mach-O 中被 dyld 绑定的 C 符号,是 iOS/macOS 逆向与底层埋点(malloc 追踪、双 close 检测、网络 socket 拦截、NSLog 重定向等)领域的"瑞士军刀"。 一、fishhook 是什么 一句话定义:fishhook 通过修改 Mach-O 镜像中 __DATA(_CONST) 段里 __la_symbol_ptr / __nl_symbol_ptr 两个 Section 中的函数指针,把对外部 C 符号(如 open、close、NSLog)的调用重定向到自定义的替换函数,同时保留原函数指针供回调使用。 它提供的能力类似 macOS 上 DYLD_INTERPOSE 宏(编译期 interpose),但: 运行时生效:不需要重新编译依赖库,随时可以在 App 启动后对系统库的 C 函数下钩子; 支持动态加载的 image:注册后对后续 dlopen 加载的 image 同样生效; 对业务零侵入:不改 App 本身的代码段,也不破坏符号表,只是重写 GOT/stub 里的一个指针。 但它也有非常明确的能力边界: 能力 是否支持 Hook 通过 dyld 动态绑定的外部 C 函数(libSystem、CoreFoundation 里的 C API、objc_msgSend 等) 是 Hook Objective-C 方法(-[NSString length]) 否(要用 Method Swizzling) Hook 当前 image 内部的 C 函数(静态链接、static、内联) 否(它不走 __la_symbol_ptr,直接是段内相对寻址) Hook Swift 方法(除非显式 @_cdecl) 否 Hook 已经解析并内联到寄存器里的符号(如 LTO 后消失的符号) 否 记住这条原则:fishhook 的作用域 = 被 dyld 绑定为"间接符号"(Indirect Symbol)的 C 符号。 ...

May 2, 2026

Texture源码导读

TODO: 待补充

May 2, 2026

Swinject源码导读

Swinject 是一款轻量级的 Swift 依赖注入(Dependency Injection)框架,灵感来自 .NET 的 Ninject。它通过类型安全的方式管理对象之间的依赖关系,将对象的创建和使用解耦。本文基于 v2.8.3 源码进行分析。 一、整体架构 Swinject 的核心设计围绕**注册-解析(Register-Resolve)**模式展开,整个框架仅约 20 个源码文件,代码极为精简。 graph TB subgraph "组织层" A["Assembler"] B["Assembly (Protocol)"] end subgraph "核心层" C["Container"] D["Resolver (Protocol)"] end subgraph "注册层" E["ServiceEntry<Service>"] F["ServiceKey"] end subgraph "作用域层" G["ObjectScope"] H["InstanceStorage (Protocol)"] end subgraph "存储实现" I["TransientStorage"] J["GraphStorage"] K["PermanentStorage"] L["WeakStorage"] end subgraph "包装器" M["Lazy<Service>"] N["Provider<Service>"] end A -->|"管理"| B B -->|"assemble(container:)"| C C -->|"实现"| D C -->|"存储注册信息"| E E -->|"由 ServiceKey 索引"| F E -->|"关联"| G G -->|"创建"| H H --- I H --- J H --- K H --- L C -->|"延迟解析"| M C -->|"每次新建"| N 源码文件结构: ...

May 2, 2026

SnapKit源码导读

SnapKit 是 iOS / macOS 社区最广泛使用的 Auto Layout DSL 库,由 Robert Payne 等人维护,目前在 GitHub 已收获 20k+ star。它在 NSLayoutConstraint 之上构建了一整套链式、类型安全、面向协议的声明式约束语法。本文基于 v5.7.1(2025 年 2 月发布,最后 master commit 2025-05-08 19f59a6)源码进行分析,代码总量不足 3500 行,却用一个分级的 Builder 链路,把 NSLayoutConstraint 那套冗长的 API 封装成了今天我们习惯的 make.left.equalTo(x).offset(10)。 一、整体架构 SnapKit 的核心设计可以用一句话概括:用编译期强约束的链式 Builder,驱动一个延迟构建的 ConstraintDescription,最终在闭包结束后一次性生成 NSLayoutConstraint 并激活。 graph TB subgraph "DSL 入口层" V["UIView / NSView / UILayoutGuide"] SNP["view.snp(ConstraintViewDSL)"] MK["makeConstraintsremakeConstraintsupdateConstraints"] end subgraph "Builder 链(阶段化构建器)" CM["ConstraintMaker(属性起点 make.left)"] CME["ConstraintMakerExtendable(追加属性 .top.right)"] CMR["ConstraintMakerRelatable(关系 equalTo/greaterThan)"] CMED["ConstraintMakerEditable(multipliedBy/offset/inset)"] CMP["ConstraintMakerPrioritizable(priority)"] CMF["ConstraintMakerFinalizable(labeled)"] end subgraph "描述 & 协议层" DESC["ConstraintDescription(懒加载装配)"] TARGETS["Target 协议族Relatable/Constant/OffsetInset/Multiplier/Priority"] ATTR["ConstraintAttributes(OptionSet 位运算)"] end subgraph "约束层" C["Constraint(封装一组 LayoutConstraint)"] LC["LayoutConstraint(NSLayoutConstraint 子类)"] ITEM["LayoutConstraintItem(UIView/UILayoutGuide)"] end subgraph "系统层" NSLC["NSLayoutConstraint.activate"] end V -->|扩展属性| SNP SNP --> MK MK -->|创建| CM CM -->|返回| CME CME -->|继承| CMR CMR -->|equalTo| CMED CMED -->|继承| CMP CMP -->|priority| CMF CM -.写入.-> DESC CME & CMR & CMED & CMP & CMF -.读写.-> DESC DESC -.lazy 构建.-> C C --> LC C -.关联对象.-> ITEM DESC -.使用.-> ATTR CMR -.类型匹配.-> TARGETS C --> NSLC 源码目录(Sources/,约 3500 行) Sources/ ├── ConstraintView.swift # typealias UIView/NSView → ConstraintView ├── ConstraintLayoutGuide.swift # typealias UILayoutGuide/NSLayoutGuide ├── ConstraintLayoutSupport.swift # typealias UILayoutSupport ├── Typealiases.swift # LayoutRelation / LayoutAttribute / LayoutPriority ├── ConstraintConfig.swift # interfaceLayoutDirection 全局开关 │ ├── ConstraintView+Extensions.swift # view.snp 入口(含 snp_ 老别名) ├── ConstraintLayoutGuide+Extensions.swift # guide.snp 入口 ├── UILayoutSupport+Extensions.swift # topLayoutGuide.snp(iOS 11 前) ├── ConstraintViewDSL.swift # makeConstraints / remake / update / remove ├── ConstraintLayoutGuideDSL.swift # LayoutGuide 版 DSL ├── ConstraintLayoutSupportDSL.swift # LayoutSupport 版 DSL ├── ConstraintDSL.swift # DSL 协议 + 属性定义(left/top/edges/...) │ ├── ConstraintMaker.swift # 属性起点(make.left / make.edges / ...) ├── ConstraintMakerExtendable.swift # 可追加属性(.top.right) ├── ConstraintMakerRelatable.swift # equalTo / lessThanOrEqual / greaterThanOrEqual ├── ConstraintMakerRelatable+Extensions.swift # equalToSuperview 闭包版 ├── ConstraintMakerEditable.swift # multipliedBy / dividedBy / offset / inset ├── ConstraintMakerPrioritizable.swift # priority(...) ├── ConstraintMakerFinalizable.swift # labeled(...) + .constraint │ ├── ConstraintAttributes.swift # OptionSet 属性位图 ├── ConstraintRelation.swift # equal / lessThanOrEqual / greaterThanOrEqual ├── ConstraintPriority.swift # required/high/medium/low ├── ConstraintDescription.swift # Builder 中间态 + lazy Constraint ├── ConstraintItem.swift # (target, attributes) 二元组(弱引用) ├── LayoutConstraintItem.swift # UIView/UILayoutGuide 统一协议 ├── ConstraintInsets.swift # UIEdgeInsets typealias ├── ConstraintDirectionalInsets.swift # NSDirectionalEdgeInsets(iOS 11+) │ ├── ConstraintRelatableTarget.swift # 可作为 equalTo 参数的类型标记 ├── ConstraintConstantTarget.swift # constant 目标 + 属性→CGFloat 的派发 ├── ConstraintOffsetTarget.swift # offset 目标 ├── ConstraintInsetTarget.swift # inset 目标(含标量→insets 转换) ├── ConstraintDirectionalInsetTarget.swift # 方向 insets 目标 ├── ConstraintMultiplierTarget.swift # multiplier 目标 ├── ConstraintPriorityTarget.swift # priority 目标(Int/Float/UILayoutPriority) │ ├── Constraint.swift # 真正的约束对象 + activate/deactivate ├── LayoutConstraint.swift # NSLayoutConstraint 子类,回指 Constraint ├── Debugging.swift # LayoutConstraint.description 美化 └── PrivacyInfo.xcprivacy # 隐私清单 核心设计思想 模式 作用 源码体现 阶段化 Builder(Staged Builder / Step Builder) 通过继承链控制链式调用的合法顺序,让 make.left.offset(10).equalTo(...) 这类非法组合在编译期报错 ConstraintMaker → Extendable → Relatable → Editable → Prioritizable → Finalizable 协议 + 类型擦除(POP) 让 Int / Float / CGFloat / CGSize / CGPoint / UIEdgeInsets / UIView / ConstraintItem 等异构类型可以统一作为 equalTo / offset / priority 的参数 Constraint*Target 协议族 OptionSet 位运算 用 32 位整型表达「属性集合」,.edges = [.left, .top, .right, .bottom] 这类聚合用位或实现,避免反复建数组 ConstraintAttributes Associated Object 在不污染 UIView 原类的前提下,为每个 View 绑定「它持有的 SnapKit Constraint 集合」和「snp.label」 LayoutConstraintItem.constraintsSet + ConstraintDSL.setLabel Lazy 求值 ConstraintDescription.constraint 是 lazy var,闭包执行结束前只是收集参数,真正的 Constraint 构造和 NSLayoutConstraint 生成都推迟到最后 ConstraintDescription.constraint 跨平台 typealias 通过一组 typealias 把 iOS / macOS / tvOS 的 UIView/NSView、UIEdgeInsets/NSEdgeInsets、UILayoutPriority/NSLayoutConstraint.Priority 等抹平 ConstraintView.swift / Typealiases.swift 二、DSL 入口:view.snp SnapKit 没有像 Masonry 那样污染 UIView 的命名空间,而是通过一个命名空间结构体暴露 DSL。 ...

May 2, 2026