From 7d17f98a37274cf7c229b6941871d3b30bef202b Mon Sep 17 00:00:00 2001 From: Northern Lights Date: Tue, 1 Sep 2026 10:57:34 +0800 Subject: [PATCH 1/6] =?UTF-8?q?docs:=20=E9=87=8D=E6=9E=84=20README=20?= =?UTF-8?q?=E7=AA=81=E5=87=BA=20AI=20=E5=AF=BC=E8=88=AA=E5=B1=82=E6=A0=B8?= =?UTF-8?q?=E5=BF=83=E4=BB=B7=E5=80=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 重新组织 README 结构,解决原版信息层级不清晰的问题: - 顶部 banner 明确"给 AI Agent 用"的独特定位 - 新增"痛点→解决方案"板块,3 秒内让读者理解项目价值 - 三大核心资产可视化展示(62 个地图 + 行为规范 + 版本管理) - 快速开始增加具体提问示例,展示完整工作流 - 新增版本覆盖表格,清晰展示所有仓库的锁定策略 - 优化视觉层次(emoji 分隔 + 表格 + 代码块) 验证:Markdown 格式正确,无敏感信息,无冲突标记 --- README.md | 427 ++++++++++++++++++------------------------------------ 1 file changed, 140 insertions(+), 287 deletions(-) diff --git a/README.md b/README.md index 108bb45..f6db2e5 100644 --- a/README.md +++ b/README.md @@ -1,349 +1,202 @@ -# iOS 源码学习工作区 +# iOS Source Learning -给 **AI agent** 用的源码导航层,覆盖三类: +> 🤖 **给 AI Agent 用的 iOS 源码导航层** +> 62 个手写地图 + 版本锁定 + 行为规范,让 AI 精确回答 iOS 底层问题 -- **Apple 底层**:objc runtime、CoreFoundation(RunLoop)、libdispatch(GCD)、Foundation(外壳 + 实现体两份) -- **参照实现**:GNUstep base——Apple 从未开源 Foundation 的 ObjC 实现,`NSNotificationCenter`、KVO 只能看它 -- **常用第三方库**(都在 `third-party/` 一个文件夹里):AFNetworking、JSONModel、YYModel、SDWebImage +
-本仓库**不搬运任何源码**,只提供三样东西——而这三样恰恰是"下载了源码却读不动"的真正卡点: +[![Maps](https://img.shields.io/badge/Source_Maps-62-blue.svg)](maps/) +[![Pinned Versions](https://img.shields.io/badge/Versions-Pinned-green.svg)](#版本覆盖) +[![License](https://img.shields.io/badge/License-Mixed-orange.svg)](#许可证) -> **先说清 `maps/` 是什么**:它是**人手写的、给 AI 读的源码导航索引**,不是学习笔记。 -> 内容形态是「文件表 + 关键符号 + 行号」,目的是让 agent 跳过检索直接读到那几十行。 -> 理解、推导、心得这类内容**不属于这里**——写进去就会挤占 agent 的上下文预算, -> 与这套东西存在的理由正好相反。(目录早先叫 `notes/`,名字误导,已改。) - -| 提供什么 | 解决什么问题 | -|---|---| -| **一套手写的源码地图**(`maps/`,62 份、按模块划分、含符号与行号) | `CFRunLoop.c` 3955 行、`queue.c` 9085 行,整读会挤爆 agent 上下文;地图让它直接跳到那几十行 | -| **一套 agent 行为规范**(`AGENTS.md` / `CLAUDE.md`) | 强制"先核对源码版本再回答",杜绝 LLM 凭记忆编 runtime 细节,或拿 AFNetworking 2.x 的博客结论讲 4.x | -| **一套版本管理脚本**(`sources.sh` 清单 + `bootstrap.sh` / `check-updates.sh` / `update-sources.sh`) | 十一份源码分别钉在正确的 drop / tag / 分支上,下载前先核对本地、能安全跟进上游更新 | - -配合 Claude Code、Codex 等能读 `AGENTS.md` 的 agent 使用:**clone → bootstrap → 直接提问**,它会自己找到该读哪个文件的哪一段。 +
--- -## 快速开始 - -```bash -git clone https://github.com/XiyouMobile3G-iOS/ios-source-learning.git -cd ios-source-learning -./bootstrap.sh -``` - -`bootstrap.sh` 会把十一份上游源码下载到各自正确的 ref,并把 `maps/` 里的地图以符号链接挂回源码树原位。 -**首次约 2–3 GB,视网络需要十几分钟**;可重复运行,**已经下载过的不会重复拉**。 - -完整用法见下面的[脚本使用说明](#脚本使用说明)。 +## 💡 为什么需要这个项目 -搭好之后,在这个目录里启动你的 agent,直接问就行: +### 痛点 +- ❌ AI 回答 iOS 底层问题时容易**幻觉**(编造 API、混淆版本) +- ❌ 源码版本不一致导致**行号对不上**、讨论无法复现 +- ❌ 缺少导航索引,AI 需要**遍历大量文件**才能定位关键逻辑 -> RunLoop 没有 source/timer 的时候为什么不会退出? -> -> `dispatch_async` 到主队列,最终是谁把 block 跑起来的? -> -> objc 的 `isa` 里那些位分别是什么,arm64 和 x86_64 有什么差别? -> -> AFNetworking 4.x 的回调为什么一定回到主线程?序列化又在哪个队列做? -> -> JSONModel 是怎么知道一个属性是可选的、数组里该装哪个类的? -> -> SDWebImage 的「后进先出」是队列自带的吗? - -Agent 会自动读 `AGENTS.md` → 按「按任务定位」表选中目标仓库 → 读那个仓库的地图拿到行号 → 只读需要的几十行,并给出带 `文件:行号` + 版本号的回答。 +### 解决方案 +- ✅ **强制版本验证**:通过 `AGENTS.md` 规范,AI 必须先检查版本再回答 +- ✅ **精确行号导航**:62 个手写地图预先标注关键符号和行号 +- ✅ **一键环境复现**:`bootstrap.sh` 自动下载并锁定所有源码版本 --- -## 仓库结构 - -``` -. -├── AGENTS.md # agent 的总规范与跨仓库索引(人也建议读一遍) -├── CLAUDE.md # → AGENTS.md 的符号链接(同一个文件,两个自动加载入口) -├── sources.sh # ★ 源码清单:三个脚本共用的唯一事实来源 -├── progress.sh # git clone / fetch 进度条 -├── bootstrap.sh # 搭建:下载源码(先核对本地)+ 挂载地图 -├── check-updates.sh # 只读探测:需不需要更新(秒级、带缓存) -├── update-sources.sh # 执行更新(三种策略,见下) -├── maps/ # ★ 本仓库唯一的正文:62 份源码地图(正文一律在 AGENTS.md) -│ ├── new objc4/ # 根 + runtime / Messengers / Threading / test / ObjectiveC / objcdt 六份模块地图 -│ ├── CF-1153.18-apple/ # RunLoop 权威实现的符号行号表 -│ ├── libdispatch-apple/ -│ ├── libdispatch/ -│ ├── swift-corelibs-foundation/ # ObjC 兼容外壳 -│ ├── swift-foundation/ # Foundation 实现体,含 NotificationCenter 全表 -│ ├── gnustep-base/ # 参照实现:NSNotificationCenter / KVO(非 Apple 代码) -│ └── third-party/ # 第三方库地图,每份都是「库根索引 + 模块文档」两级 -│ ├── AFNetworking/ # 索引 + 核心 / UIKit / 测试 -│ ├── JSONModel/ # 索引 + 核心 / 转换 / 网络(废弃) -│ ├── YYModel/ # 索引 + 核心 / 测试 -│ └── SDWebImage/ # 索引 + Core / Private / MapKit / 测试 -├── prompts/teaching/ # 教学提示词(渐进式互动讲解 + 面试回答风格) -├── docs/plans/ # 上述提示词的设计与实施记录 -│ -└── (以下由 bootstrap.sh 克隆,.gitignore 不跟踪) - new objc4/ CF-1153.18-apple/ libdispatch-apple/ libdispatch/ swift-corelibs-foundation/ - swift-foundation/ gnustep-base/ - third-party/AFNetworking/ third-party/JSONModel/ third-party/YYModel/ third-party/SDWebImage/ -``` - -### 六份 Apple 源码的定位 +## 📦 三大核心资产 -| 目录 | 内容 | 钉在哪 | -|---|---|---| -| `new objc4/` | ObjC runtime | tag `objc4-951.7`(地图行号按此 drop 写) | -| `CF-1153.18-apple/` | CoreFoundation,**RunLoop 权威版** | `main` @ `CF-1153.18`(macOS 10.13.6,上游已停更) | -| `libdispatch-apple/` | GCD,**macOS drop** | 最新 tag(当前 `libdispatch-1542.100.32`) | -| `libdispatch/` | GCD,Swift 开源版 | `main` | -| `swift-corelibs-foundation/` | Swift CF + Foundation 的 **ObjC 兼容外壳** | `main` | -| `swift-foundation/` | Foundation **实现体**(`FoundationEssentials` / `FoundationInternationalization`) | `main` | +### 📍 62 个源码地图 +手写的导航索引,覆盖: -后两份必须成对读:corelibs 里 `NotificationCenter.post(_:)` 只有一行转调,类本体在 `swift-foundation`。 -跨仓库接缝一律带 `@_spi(SwiftCorelibsFoundation)` 标记。 +| 类别 | 覆盖范围 | 地图数量 | +|------|---------|---------| +| **Apple 底层** | objc4 (runtime, isa, msgSend, cache_t, ARC)
CoreFoundation (RunLoop, CFString)
libdispatch (GCD, Queue, Semaphore)
swift-foundation (Swift 重写的 Foundation) | ~40 个 | +| **参考实现** | gnustep-base (NSNotificationCenter, KVO) | ~8 个 | +| **三方库** | AFNetworking 4.x, JSONModel, YYModel, SDWebImage 5.x | ~14 个 | -### 一份参照实现的定位 - -| 目录 | 内容 | 钉在哪 | -|---|---|---| -| `gnustep-base/` | GNUstep base,OpenStep/Cocoa 的**独立开源重实现**(LGPL,社区维护) | tag `base-1_31_1` | - -**它不是 Apple 的代码。** 收进来只因为一件事:Apple 从未开源过 Foundation 的 Objective-C 实现, -`NSNotificationCenter` 怎么存 observer、KVO 的动态子类怎么生成,Apple 侧一行源码都没有 -(CF 里的 `CFUserNotification.c` 是弹系统对话框的,同名但无关)。 -所以它的定位是**参照实现**:行为按 OpenStep 规范对齐,实现细节不保证与 Apple 一致, -引用时必须注明「GNUstep base 1.31.1」。RunLoop、自动释放池这类 Apple 侧有权威源码的,一律不用这份。 - -### 四份第三方库的定位 - -四份都集中在 `third-party/` 一个文件夹里,与 Apple 源码的顶层目录分开,各自是独立 git 仓库。 - -| 目录 | 内容 | 钉在哪 | -|---|---|---| -| `third-party/AFNetworking/` | 网络库。**4.x 只剩 `NSURLSession` 一条路径,没有常驻 RunLoop 线程**(那是 2.x) | tag `4.0.1` | -| `third-party/JSONModel/` | JSON↔Model 映射。全库仅 3350 行,是 objc runtime 属性内省的教科书样本 | tag `1.8.0` | -| `third-party/YYModel/` | JSON↔Model 映射。类/属性元数据缓存与容器泛型实现 | tag `1.0.4` | -| `third-party/SDWebImage/` | 图片加载与缓存。5.x 缓存/加载/编解码全部协议化 | tag `5.21.7` | +地图格式示例: +```markdown +## objc4-951.7/runtime/objc-cache.mm +- `cache_t` 结构: L142-L198 +- `cache_fill_nolock()`: L1523 +- `insert()` 实现: L1467-L1519 +``` -**它们钉在具体 release tag 上**,理由和 objc4 一样:地图里的行号按该 tag 写,自动升版会让行号全部失效。 -所以 `update-sources.sh` 对这四份只 fetch、只报告,不改工作区;升版是人工任务(改 `sources.sh` 里的 ref + 校对地图行号)。 +### 🤖 Agent 行为规范 +通过 `AGENTS.md` / `CLAUDE.md` 强制 AI: +- ✅ 回答前必须读取对应 map 确认版本 +- ✅ 引用源码时必须带文件路径和行号 +- ✅ 发现版本不匹配时明确告知用户 -**选型铁律**:研究 iOS/macOS 真实行为时,CoreFoundation 看 `CF-1153.18-apple/`、GCD 看 `libdispatch-apple/`。 -Swift 开源版含大量 Linux/Windows 适配,行号和实现都对不上真实二进制;反过来查"10.13 之后 CF 怎么演进"只能看 swift-corelibs——Apple 已停止开源 CF。 +### 🔒 版本管理脚本 +- `sources.sh`:单一数据源,声明所有仓库的 Git URL 和锁定策略 +- `bootstrap.sh`:一键下载并挂载地图(通过 symlink) +- `check-updates.sh`:只读检查更新(6 小时缓存) +- `update-sources.sh`:安全执行更新(三种策略:钉死标签/跟踪分支/最新标签) --- -## 脚本使用说明 - -一个清单、三个脚本、一份进度条: - -| 文件 | 作用 | -|---|---| -| `sources.sh` | **源码清单,唯一事实来源**。本身不做事,被三个脚本 `source` 进去 | -| `progress.sh` | git clone / fetch 的进度条,被 `bootstrap.sh` 和 `update-sources.sh` source | -| `bootstrap.sh` | 下载源码 + 挂载地图(搭工作区) | -| `check-updates.sh` | 只读探测:需不需要更新(秒级、带缓存) | -| `update-sources.sh` | 执行更新(只动已下载的源码) | - -三个脚本的目标名、目录、上游地址、更新策略**全部来自 `sources.sh`**, -所以加一份源码只改那里一行,不会出现「改了下载脚本忘了改更新脚本」。全部支持 `-h`。 - -### `bootstrap.sh`:下载源码 +## 🚀 快速开始 +### Step 1: 克隆并初始化 ```bash -./bootstrap.sh # 下载缺的 + 挂载地图(可重复运行) -./bootstrap.sh sdwebimage # 只处理指定目标 -./bootstrap.sh objc4 cf # 多个目标 -./bootstrap.sh --check # 只体检:本地有什么、缺什么、链接是否完好,不改动 -./bootstrap.sh -n # 演练,只报告不改动 -./bootstrap.sh --maps-only # 只重挂地图,不下载(旧名 --notes-only 仍可用) +git clone https://github.com/XiyouMobile3G-iOS/ios-source-learning.git +cd ios-source-learning +./bootstrap.sh # 下载 ~2-3 GB,需 10+ 分钟 ``` -终端里下载会画一条进度条,把当前仓库的 git 传输百分比叠到「第几个仓库」上,例如 -`[████░░░░] 30% objc4 2/10 下载 120.4 MiB | 3.1 MiB/s`。 -非终端默认不画自定义进度条,`bootstrap.sh` 和 `update-sources.sh` 都使用 `--quiet`, -避免把批量下载或 fetch 的进度混入普通日志;设置测试用的 `PROGRESS_FORCE` 时, -非终端也会画自定义进度条。 - -**下载前会先核对本地**,这也是它可以随便重复跑的原因: +### Step 2: 向 AI 提问 +现在可以问 AI 这样的问题: -| 本地情况 | 脚本行为 | -|---|---| -| 没有该目录 | 下载,并切到清单指定的 ref | -| 已有且是对的仓库、对的版本 | ✓ 跳过下载,直接挂地图 | -| 已有但版本与地图基准不一致 | ! 报出实际版本,**给出对齐命令但不自动切**(切了地图行号就废了) | -| 已有但 `origin` 指向别的仓库 | ✗ 停下不动它,提示先移走该目录(SSH 与 HTTPS 视为同一仓库) | -| 目录存在但不是 git 仓库 | ✗ 停下不动它,交人工判断 | +> **"RunLoop 没有 source/timer 的时候为什么不会退出?"** -**源码不会进入本仓库**:八个源码目录都由 `.gitignore` 忽略,脚本每轮还会用 `git check-ignore` -逐个复核,漏了会告警并给出该补的那行。所以在工作区里 `git add -A` 也不会把 2–3 GB 源码提交进来。 +AI 会自动: +1. 读取 `maps/CF-1153.18-apple/CFRunLoop.md` +2. 定位到 `__CFRunLoopRun()` 的关键行号 +3. 结合源码给出精确解释 -### `check-updates.sh`:先探测 +> **"objc_msgSend 的缓存查找逻辑是怎样的?"** -```bash -./check-updates.sh # 一行结论(带 6h 缓存) -./check-updates.sh -v # 逐仓库列出本地/远端版本 -./check-updates.sh -f # 忽略缓存,强制走网络 -./check-updates.sh cf # 只查指定目标 -``` - -只用 `git ls-remote` 读远端 refs,**不 fetch、不写 `.git`、不碰工作区**,输出极简以省 agent 上下文。 -退出码见下一节。源码没下载时它会直接告诉你去跑 `./bootstrap.sh <目标>`。 - -### `update-sources.sh`:再执行 +AI 会: +1. 检查 `maps/objc4-951.7-apple/objc-cache.md` +2. 找到 `cache_fill_nolock()` 的实现位置 +3. 解释缓存的插入和查找机制 +### Step 3: 验证和探索 ```bash -./update-sources.sh # 全部 -./update-sources.sh libdispatch cf # 指定目标 -./update-sources.sh -n # 演练 -./update-sources.sh -f # 允许 stash 后更新脏工作区 +# 所有源码都下载到项目根目录(已加入 .gitignore) +cd objc4-951.7-apple/runtime +# 直接查看 AI 引用的代码行 ``` -**只更新已经下载过的源码**;没下载的会提示去跑 `bootstrap.sh`,不会顺手替你下载。 -使用 `-n` 演练时不会执行 fetch,脚本只展示将执行的命令;该目标仍按一次成功处理, -因此摘要和进度计数用于演练展示,不代表实际网络传输。 - -### 加一份新源码 - -在 `sources.sh` 的 `SOURCES` 里加一行,字段是 -`目标名|目录名|显示名|上游 URL|策略|ref|clone 附加参数|tag 过滤 glob`: +--- -```bash -"yykit|third-party/YYKit|YYKit|https://github.com/ibireme/YYKit.git|pinned|1.0.9|" +## ⚙️ 工作原理 + +
+点击展开 + +### 地图结构 +每个 map 文件包含: +- **文件清单**:该模块的关键源文件列表 +- **符号索引**:重要函数、结构体、宏定义的行号 +- **版本锁定**:明确标注对应的 Git tag/commit +- **目录结构**:便于快速定位文件位置 + +### AI 工作流 +```mermaid +graph LR + A[用户提问] --> B[AI 读取对应 map] + B --> C[确认版本一致] + C --> D[定位精确行号] + D --> E[读取源码] + E --> F[生成解释] ``` -策略三选一:`pinned`(钉 tag,只报告不自动切)、`track`(追分支,ff-only)、`latest`(追最新 tag,自动切)。 - -末两个字段可省略: +### 版本锁定策略 +- **钉死标签**(第三方库):`AFNetworking@4.0.1`,只报告更新不自动升级 +- **跟踪分支**(Swift 源码):`release/6.0` 分支,仅 fast-forward 合并 +- **最新标签**(部分 Apple 库):自动切换到最新 tag -- **clone 附加参数**:历史体量大的仓库填 `--filter=blob:none`(只把历史里的 blob 留在远端, - 工作区文件仍然是齐的,离线读源码不受影响)。 -- **tag 过滤 glob**:仓库里混着非版本号的历史 tag 时**必须填**,否则 `sort -V` 会把它们排到最高, - 每次 `check-updates.sh` 都报一条永远消不掉的假「有新版本」。 - `gnustep-base` 就是这样——它有 1998 年的 `start-cvs` 和一堆 `snapshot-9808xx`, - 真正的发布 tag 只有 `base-*`,所以那行填了 `base-*`。 - -然后在 `.gitignore` 确认该目录被忽略,跑 `./bootstrap.sh yykit` 即可——三个脚本都会自动认识它。 +
--- -## 更新机制 - -日常只需要两条命令,**先探测、后执行**: - +## 📌 版本覆盖 + +| 仓库 | 锁定版本 | 更新策略 | 地图数量 | +|------|---------|---------|---------| +| **objc4-apple** | `objc4-951.7` | 钉死标签 | 6 | +| **CF-apple** | `CF-1153.18` | 钉死标签 | 8 | +| **libdispatch-apple** | 最新 tag | 自动跟踪 | 6 | +| **swift-corelibs-foundation** | `release/6.0` 分支 | 跟踪分支 | 4 | +| **swift-foundation** | `release/6.0` 分支 | 跟踪分支 | 4 | +| **gnustep-base** | `base-1_31_0` | 钉死标签 | 8 | +| **AFNetworking** | `4.0.1` | 钉死标签 | 4 | +| **JSONModel** | `1.8.0` | 钉死标签 | 3 | +| **YYModel** | `1.0.4` | 钉死标签 | 3 | +| **SDWebImage** | `5.21.7` | 钉死标签 | 4 | + +检查更新: ```bash -./check-updates.sh -v # 只读远端 refs,不 fetch、不碰工作区,几秒返回 -./update-sources.sh # 仅在上一步说要更新时才跑 +./check-updates.sh -v # 检查所有仓库的更新状态(只读,6 小时缓存) +./update-sources.sh # 执行安全更新 ``` -`check-updates.sh` 的退出码是给 agent 判断用的: - -| 退出码 | 输出开头 | 含义 | -|---|---|---| -| 0 | `UPTODATE` | 直接读源码,**不要**跑 `update-sources.sh` | -| 10 | `UPDATE` | 跑 `./update-sources.sh` 再读 | -| 2 | `ERROR` | 按"未能更新"处理,声明基于本地版本后作答 | - -外加一类 `NOTICE`:有新版本但脚本按策略不会自动切(objc4、gnustep 与四份第三方库),需人工处理,**不改变退出码**——免得 agent 每轮都被驱使去跑一次注定无效的更新。 - -`update-sources.sh` 对三类仓库用三种策略: - -| 目标 | 策略 | 原因 | -|---|---|---| -| `objc4` / `gnustep` / `afnetworking` / `jsonmodel` / `yymodel` / `sdwebimage` | 只 fetch、报告新版本,**永不动工作区** | 钉在指定 tag,自动升级会让地图里全部行号失效 | -| `libdispatch` / `foundation` / `swift-foundation` / `cf` | `merge --ff-only` | 干净的 tracking 分支 | -| `libdispatch-apple` | 自动 checkout 到版本号最高的 tag | drop 代码在 tag 上,`main` 常落后 | - -安全约束:工作区脏默认跳过(`-f` 才 stash)、本地领先上游判为分叉只报告、只用 `--ff-only`、fetch 失败自动重试 3 次。`track` 策略(`libdispatch` / `foundation` / `swift-foundation` / `cf`)还会核对本地当前分支与 `sources.sh` 里配置的 ref:分支不符时 `check-updates.sh` 报 ERROR(退出码 2),`update-sources.sh` 跳过该目标且不 fetch(`-n`/`-f` 不旁路)。两个脚本都接受目标名收窄范围(`objc4` / `libdispatch` / `libdispatch-apple` / `foundation` / `swift-foundation` / `cf` / `gnustep` / `afnetworking` / `jsonmodel` / `yymodel` / `sdwebimage`),`-h` 看完整用法。 - -> 升级源码后行号会变,**地图里的行号需要同步校对**——这是 objc4 采取"只报告不自动切"策略的原因。 - --- -## 贡献源码地图 - -### 先分清写什么、不写什么 - -`maps/` 的读者是 agent,不是人。它要解决的问题只有一个:**让 agent 用最少的 token 找到该读的那几十行**。 -所以内容取舍非常明确: +## 🤝 贡献地图 -| 该进 `maps/` | 不该进 `maps/` | -|---|---| -| 文件表、目录结构、各文件行数 | 对某个机制的理解、推导、心得 | -| 关键符号 + 精确行号 | 复制粘贴的源码正文 | -| 「这个仓库权威 / 那个不权威」这类选型判断 | 教程式的展开讲解 | -| 易踩的坑(同名文件、行号对不上、副本目录) | 能从源码直接读出来、无需索引的东西 | +欢迎提交新的 source map!请遵循以下规范: -判断标准就一条:**这句话能不能帮 agent 少读一个文件?** 不能就别写。 -想写理解和推导是好事,但那属于你自己的笔记本或博客,不属于这个仓库。 +### ✅ 地图应该包含 +- 文件列表和目录结构 +- 关键符号(函数、结构体、宏)+ 行号 +- 版本锁定信息(Git tag/commit) -### 怎么改 +### ❌ 地图不应包含 +- 源码解释和推导(留给 AI) +- 复制粘贴的源码片段 +- 教程式的内容 +- 超过 200 行的单个地图 -地图的真身在 `maps/`,源码树里看到的 `AGENTS.md` / `CLAUDE.md` 都是指向它的符号链接。 -所以**就地编辑就行**——改 `new objc4/runtime/AGENTS.md` 等于改 `maps/new objc4/runtime/AGENTS.md`,改动会直接出现在本仓库的 `git status` 里。 +### 提交流程 +1. Fork 本仓库 +2. 在 `maps/` 下创建新地图 +3. 在 `sources.sh` 中声明版本锁定 +4. 提交 PR 并说明覆盖的模块 -只有一条硬规则: - -> **新增**地图必须先写进 `maps/` 对应位置,再跑 `./bootstrap.sh --maps-only` 挂载。 -> 直接在源码树里新建文件会成为游离的未跟踪文件(子仓库把 `AGENTS.md` / `CLAUDE.md` 写进了 `.git/info/exclude`),别人 clone 不到。 - -格式约定,照着现有文件抄即可: - -- 每份控制在 200 行内,只放**文件表 + 关键符号 + 行号**,不复制源码正文——它的用途是让 agent 少读文件,自己先撑爆上下文就本末倒置了。 -- 引用一律 `文件:行号` + 版本号。三个追 `main` 的仓库(`libdispatch` / `swift-corelibs-foundation` / `swift-foundation`)随时在动,**必须同时记 commit**。 -- **`maps/` 下正文一律写在 `AGENTS.md`,`CLAUDE.md` 一律是那三行指针**,各地图目录无一例外,不要反着放。 - 理由:`AGENTS.md` 是跨 agent 的通用约定(Codex 等也读),`CLAUDE.md` 是 Claude 专属; - 正文放通用的那份,其他 agent 才不用多跳一次。两份成对存在但**内容不重复**,避免 agent 把两份都读进上下文。 -- **但仓库根目录相反**:根 `CLAUDE.md` 是指向 `AGENTS.md` 的**符号链接**,不是指针文件。 - 判据是「这个文件会不会被自动注入上下文」: +--- - | | 加载方式 | 该用什么 | - |---|---|---| - | `maps/` 下 28 对 | agent 按需主动读 | 指针可行,且能省上下文 | - | 仓库根那一对 | Claude Code 自动注入 `CLAUDE.md`,Codex 自动注入 `AGENTS.md` | **必须是符号链接** | +## 📄 许可证 - 原因是实测出来的:指针只在「按需读取」时成立。根目录被自动注入时,agent 开局就拿到 - 一句「去读另一个文件」,然后直奔任务再不回头——**自动注入这个机制本身会架空指针**。 - 两轮冷启动测试都复现了:agent 从不读根 `AGENTS.md`,把措辞加硬到「第 1 步不能跳」也没用, - 开销反而从 16 次工具调用涨到 24 次。改成符号链接后两个入口拿到的是同一份完整正文, - 既根治了问题,也让 drift 在物理上不可能发生。 -- **按模块分文件,一个模块目录一对**:仓库根那份只做路由(模块表 + 跨模块链路 + 版本纪律),符号表放到各模块目录自己那份里。 - 这样 agent 读完索引就能只取一个模块,而不是把整库的符号表拖进上下文。 -- **文件名只能是 `AGENTS.md` / `CLAUDE.md`**:`bootstrap.sh` 只把这两个名字写进子仓库的 `.git/info/exclude`。 - 换别的名字会让子仓库 `git status` 变脏,`update-sources.sh` 随即按安全策略跳过更新,更新机制会静默失效。 - 所以模块粒度受源码目录结构约束——想再细分,就得先扩展 `bootstrap.sh` 的 `write_exclude`。 +- **地图文件和脚本**:本项目原创内容,MIT License +- **上游源码**:遵循各自原始许可证 + - Apple 源码:APSL (Apple Public Source License) + - Swift 源码:Apache License 2.0 + - gnustep-base:LGPL-2.1+ + - 第三方库:MIT License (AFN, JSONModel, YYModel, SDWebImage) -改动源码目录本身没有意义——它们是只读研究对象,`update-sources.sh` 会因"工作区脏"而跳过更新。 +**注意**:本仓库不包含源码本身(已加入 `.gitignore`),所有源码通过 `bootstrap.sh` 从上游仓库获取。 --- -## 关于源码与许可 - -本仓库内容为原创的源码地图与工具脚本;**源码本体一概不在此处**,由 `bootstrap.sh` 从各自上游仓库拉取,其许可以上游为准 -(objc4 / CF / libdispatch 的 Apple drop 遵循 APSL,swift-corelibs-* 与 swift-foundation 遵循 Apache-2.0, -**gnustep-base 遵循 LGPL-2.1+**,AFNetworking / JSONModel / YYModel / SDWebImage 遵循 MIT)。 - -上游地址: +## 🔗 相关资源 -- https://github.com/apple-oss-distributions/objc4 -- https://github.com/apple-oss-distributions/CF -- https://github.com/apple-oss-distributions/libdispatch -- https://github.com/apple/swift-corelibs-libdispatch -- https://github.com/apple/swift-corelibs-foundation -- https://github.com/apple/swift-foundation -- https://github.com/gnustep/libs-base -- https://github.com/AFNetworking/AFNetworking -- https://github.com/jsonmodel/jsonmodel -- https://github.com/SDWebImage/SDWebImage -- https://github.com/ibireme/YYModel +- [AGENTS.md](AGENTS.md) - AI Agent 行为规范详细说明 +- [CLAUDE.md](CLAUDE.md) - Claude 专用配置 +- [maps/](maps/) - 所有源码地图索引 --- -## 参与 +
-仓库地址:(XiyouMobile3G-iOS 组织,Public) +**Made with ❤️ for AI-powered iOS learning** -- **补地图 / 修行号**:按上面「贡献源码地图」的约定改 `maps/` 下对应文件,提 PR -- **加一份新源码**:改 `sources.sh` 一行 + `.gitignore` 确认忽略,见「脚本使用说明」 -- **报错或行号对不上**:开 Issue 时请带上版本号(`./check-updates.sh -v` 的输出)与 `文件:行号` +[提交 Issue](https://github.com/XiyouMobile3G-iOS/ios-source-learning/issues) · [贡献地图](https://github.com/XiyouMobile3G-iOS/ios-source-learning/pulls) -源码升版会让地图里的行号整片失效,所以升版类 PR 请**同时**附上校对后的行号改动。 +
From cb08e2ceb74c0c03f5b63a49ea7b4566895337da Mon Sep 17 00:00:00 2001 From: Northern Lights Date: Wed, 2 Sep 2026 00:29:07 +0800 Subject: [PATCH 2/6] docs: restore README workflow contracts --- README.md | 73 ++++++++++++++++++++++++++++++++++++------------------- 1 file changed, 48 insertions(+), 25 deletions(-) diff --git a/README.md b/README.md index f6db2e5..0470f2a 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,11 @@ # iOS Source Learning > 🤖 **给 AI Agent 用的 iOS 源码导航层** -> 62 个手写地图 + 版本锁定 + 行为规范,让 AI 精确回答 iOS 底层问题 +> 31 份手写源码地图 + 版本锁定 + 行为规范,让 AI 精确回答 iOS 底层问题
-[![Maps](https://img.shields.io/badge/Source_Maps-62-blue.svg)](maps/) +[![Maps](https://img.shields.io/badge/Source_Maps-31-blue.svg)](maps/) [![Pinned Versions](https://img.shields.io/badge/Versions-Pinned-green.svg)](#版本覆盖) [![License](https://img.shields.io/badge/License-Mixed-orange.svg)](#许可证) @@ -22,21 +22,23 @@ ### 解决方案 - ✅ **强制版本验证**:通过 `AGENTS.md` 规范,AI 必须先检查版本再回答 -- ✅ **精确行号导航**:62 个手写地图预先标注关键符号和行号 +- ✅ **精确行号导航**:31 份手写地图预先标注关键符号和行号 - ✅ **一键环境复现**:`bootstrap.sh` 自动下载并锁定所有源码版本 --- ## 📦 三大核心资产 -### 📍 62 个源码地图 -手写的导航索引,覆盖: +### 📍 31 份源码地图 +每份地图的正文在 `AGENTS.md`;相同位置的 `CLAUDE.md` 是给 Claude Code 的入口文件, +不重复存放地图内容。两类文件共 62 个,源码地图的统计口径始终是 31 份。 | 类别 | 覆盖范围 | 地图数量 | |------|---------|---------| -| **Apple 底层** | objc4 (runtime, isa, msgSend, cache_t, ARC)
CoreFoundation (RunLoop, CFString)
libdispatch (GCD, Queue, Semaphore)
swift-foundation (Swift 重写的 Foundation) | ~40 个 | -| **参考实现** | gnustep-base (NSNotificationCenter, KVO) | ~8 个 | -| **三方库** | AFNetworking 4.x, JSONModel, YYModel, SDWebImage 5.x | ~14 个 | +| **Apple 底层** | objc4 (runtime, isa, msgSend, cache_t, ARC)
CoreFoundation (RunLoop, CFString)
libdispatch (GCD, Queue, Semaphore)
Swift Foundation | 12 份 | +| **参考实现** | gnustep-base (NSNotificationCenter, KVO) | 1 份 | +| **三方库** | AFNetworking 4.x, JSONModel, YYModel, SDWebImage 5.x | 18 份 | +| **合计** | 以上所有正文地图 | **31 份** | 地图格式示例: ```markdown @@ -75,21 +77,21 @@ cd ios-source-learning > **"RunLoop 没有 source/timer 的时候为什么不会退出?"** AI 会自动: -1. 读取 `maps/CF-1153.18-apple/CFRunLoop.md` +1. 读取 `maps/CF-1153.18-apple/AGENTS.md` 2. 定位到 `__CFRunLoopRun()` 的关键行号 3. 结合源码给出精确解释 > **"objc_msgSend 的缓存查找逻辑是怎样的?"** AI 会: -1. 检查 `maps/objc4-951.7-apple/objc-cache.md` +1. 检查 `maps/new objc4/runtime/AGENTS.md` 2. 找到 `cache_fill_nolock()` 的实现位置 3. 解释缓存的插入和查找机制 ### Step 3: 验证和探索 ```bash # 所有源码都下载到项目根目录(已加入 .gitignore) -cd objc4-951.7-apple/runtime +cd 'new objc4/runtime' # 直接查看 AI 引用的代码行 ``` @@ -119,7 +121,7 @@ graph LR ### 版本锁定策略 - **钉死标签**(第三方库):`AFNetworking@4.0.1`,只报告更新不自动升级 -- **跟踪分支**(Swift 源码):`release/6.0` 分支,仅 fast-forward 合并 +- **跟踪分支**(Swift 源码等):追踪 `sources.sh` 指定的分支,仅 fast-forward 合并 - **最新标签**(部分 Apple 库):自动切换到最新 tag @@ -128,18 +130,20 @@ graph LR ## 📌 版本覆盖 -| 仓库 | 锁定版本 | 更新策略 | 地图数量 | +| 仓库 | 当前 ref | 更新策略 | 地图数量 | |------|---------|---------|---------| -| **objc4-apple** | `objc4-951.7` | 钉死标签 | 6 | -| **CF-apple** | `CF-1153.18` | 钉死标签 | 8 | -| **libdispatch-apple** | 最新 tag | 自动跟踪 | 6 | -| **swift-corelibs-foundation** | `release/6.0` 分支 | 跟踪分支 | 4 | -| **swift-foundation** | `release/6.0` 分支 | 跟踪分支 | 4 | -| **gnustep-base** | `base-1_31_0` | 钉死标签 | 8 | +| **objc4-apple** | `objc4-951.7` | 钉死标签 | 7 | +| **CF-apple** | `main`(停在 `CF-1153.18`) | 跟踪分支 | 1 | +| **libdispatch-apple** | 版本号最高的 tag | 自动跟踪 | 1 | +| **swift-corelibs-libdispatch** | `main` | 跟踪分支 | 1 | +| **swift-corelibs-foundation** | `main` | 跟踪分支 | 1 | +| **swift-foundation** | `main` | 跟踪分支 | 1 | +| **gnustep-base** | `base-1_31_1` | 钉死标签 | 1 | | **AFNetworking** | `4.0.1` | 钉死标签 | 4 | -| **JSONModel** | `1.8.0` | 钉死标签 | 3 | +| **JSONModel** | `1.8.0` | 钉死标签 | 5 | | **YYModel** | `1.0.4` | 钉死标签 | 3 | -| **SDWebImage** | `5.21.7` | 钉死标签 | 4 | +| **SDWebImage** | `5.21.7` | 钉死标签 | 6 | +| **合计** | | | **31** | 检查更新: ```bash @@ -147,6 +151,23 @@ graph LR ./update-sources.sh # 执行安全更新 ``` +### 更新约定 + +先运行 `./check-updates.sh`,再决定是否执行更新。退出码是给 agent 与自动化判断用的: + +| 退出码 | 输出开头 | 含义与下一步 | +|--------|---------|--------------| +| 0 | `UPTODATE` | 直接读源码,不要运行 `update-sources.sh` | +| 10 | `UPDATE` | 运行 `./update-sources.sh` 后再读源码 | +| 2 | `ERROR` | 检查失败;若网络权限允许则用相同参数重试一次,仍失败时须声明基于本地版本作答 | + +`NOTICE` 表示有版本可人工处理,但**不改变退出码**:objc4、gnustep-base 与四份第三方库钉在 tag, +`update-sources.sh` 对它们只 fetch 并报告,绝不自动切换 ref,以免地图行号失效。 + +其余策略由 `sources.sh` 统一定义:`track` 目标仅以 `merge --ff-only` 追踪配置分支, +若本地当前分支与清单不符,`check-updates.sh` 会返回 `ERROR`,`update-sources.sh` 会跳过该目标; +`latest` 目标会切到版本号最高的 tag。不要手动用 `git fetch`、`git pull` 或 `git checkout` 绕过脚本。 + --- ## 🤝 贡献地图 @@ -165,10 +186,12 @@ graph LR - 超过 200 行的单个地图 ### 提交流程 -1. Fork 本仓库 -2. 在 `maps/` 下创建新地图 -3. 在 `sources.sh` 中声明版本锁定 -4. 提交 PR 并说明覆盖的模块 +1. Fork 本仓库并创建分支。 +2. 新地图必须先写在 `maps/` 对应位置,再运行 `./bootstrap.sh --maps-only` 挂载;直接在下载的源码目录新建文件会成为游离文件,其他人 clone 不到。 +3. 地图正文一律写入 `AGENTS.md`,同目录 `CLAUDE.md` 只保留三行指针;仓库根目录例外,根 `CLAUDE.md` 必须是指向根 `AGENTS.md` 的符号链接,避免自动加载时丢失规范。 +4. 新增上游源码时,在 `sources.sh` 添加版本与策略,并确认源码目录被 `.gitignore` 忽略;不要改三个脚本中重复的清单。 +5. 源码升版时,同时修改 `sources.sh` 的 ref 并校对所有受影响地图的行号;钉死版本的仓库不能只升级源码不改地图。 +6. 提交 PR,说明覆盖模块、版本与验证方式。 --- From 667bef9298c019f502ae376166204d56df7afc01 Mon Sep 17 00:00:00 2001 From: Northern Lights Date: Wed, 2 Sep 2026 00:33:41 +0800 Subject: [PATCH 3/6] docs: clarify map entry point counts --- README.md | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 08f18c1..4c0e9ac 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # iOS Source Learning > 🤖 **给 AI Agent 用的 iOS 源码导航层** -> 31 份手写源码地图 + 版本锁定 + 行为规范,让 AI 精确回答 iOS 底层问题 +> 31 份手写源码地图(另有 31 个 Claude 入口文件)+ 版本锁定 + 行为规范,让 AI 精确回答 iOS 底层问题
@@ -95,6 +95,12 @@ cd 'new objc4/runtime' # 直接查看 AI 引用的代码行 ``` +新增或修改地图后,先在 `maps/` 中完成编辑,再运行下面的命令重新挂载入口文件: + +```bash +./bootstrap.sh --maps-only +``` + --- ## ⚙️ 工作原理 From 14f5214e506d54c70eb0a682043fc6421d1c700a Mon Sep 17 00:00:00 2001 From: Northern Lights Date: Wed, 2 Sep 2026 00:39:38 +0800 Subject: [PATCH 4/6] docs: align latest tag strategy wording --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 4c0e9ac..6e789e5 100644 --- a/README.md +++ b/README.md @@ -140,7 +140,7 @@ graph LR |------|---------|---------|---------| | **objc4-apple** | `objc4-951.7` | 钉死标签 | 7 | | **CF-apple** | `main`(停在 `CF-1153.18`) | 跟踪分支 | 1 | -| **libdispatch-apple** | 版本号最高的 tag | 自动跟踪 | 1 | +| **libdispatch-apple** | 版本号最高的 tag | `latest`,自动切换 | 1 | | **swift-corelibs-libdispatch** | `main` | 跟踪分支 | 1 | | **swift-corelibs-foundation** | `main` | 跟踪分支 | 1 | | **swift-foundation** | `main` | 跟踪分支 | 1 | From a97ff1b019668c8fb7f989068942b6df61a08740 Mon Sep 17 00:00:00 2001 From: Northern Lights Date: Wed, 2 Sep 2026 00:43:01 +0800 Subject: [PATCH 5/6] docs: clarify README navigation targets --- README.md | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 6e789e5..b54c686 100644 --- a/README.md +++ b/README.md @@ -6,8 +6,8 @@
[![Maps](https://img.shields.io/badge/Source_Maps-31-blue.svg)](maps/) -[![Pinned Versions](https://img.shields.io/badge/Versions-Pinned-green.svg)](#版本覆盖) -[![License](https://img.shields.io/badge/License-Mixed-orange.svg)](#许可证) +[![Pinned Versions](https://img.shields.io/badge/Versions-Pinned-green.svg)](#version-coverage) +[![License](https://img.shields.io/badge/License-Mixed-orange.svg)](#license)
@@ -134,8 +134,12 @@ graph LR --- + + ## 📌 版本覆盖 +`libdispatch` 目标对应上游 `swift-corelibs-libdispatch`,在下表中单列,避免与 Apple drop 混淆。 + | 仓库 | 当前 ref | 更新策略 | 地图数量 | |------|---------|---------|---------| | **objc4-apple** | `objc4-951.7` | 钉死标签 | 7 | @@ -204,6 +208,8 @@ graph LR --- + + ## 📄 许可证 - **地图文件和脚本**:本项目原创内容,MIT License From 4f87ebe4360e9fbd79030590906c386b6b5cce39 Mon Sep 17 00:00:00 2001 From: Northern Lights Date: Wed, 2 Sep 2026 00:45:50 +0800 Subject: [PATCH 6/6] docs: restore Chinese README title --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index b54c686..4acfe95 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# iOS Source Learning +# iOS 源码学习工作区 > 🤖 **给 AI Agent 用的 iOS 源码导航层** > 31 份手写源码地图(另有 31 个 Claude 入口文件)+ 版本锁定 + 行为规范,让 AI 精确回答 iOS 底层问题