架构技术评论

解读 Xcode 27 内置的 Agent Skills

Pasted image 20260610094923.png

Xcode 27 内置了 coding agent,而且苹果把 agent 的"技能包"(Skills)做成了可以导出的格式。一条命令就能把它们全部 dump 出来:

xcrun agent skills export --output-dir ~/Desktop/xcode-skills

导出后是 7 个 skill、49 个文件、约 436KB 纯 Markdown(外加一个 Python 脚本)。这些文件相当于苹果官方写给 AI 的"内部培训教材",信息量非常大——既能看到 iOS 27 SDK 的新 API,也能看到苹果是怎么做 prompt 工程的,甚至能反推出苹果内部测试时 agent 犯过哪些错。

这篇文章把这 7 个 skill 逐个拆开看一遍。

Xcode 27 内置 Agent Skills 全景:7 个 skill 分为 SwiftUI、内存与构建安全、现代化迁移、设备验证四组

Skill 的文件结构

每个 skill 是一个目录,结构和 Anthropic 的 Agent Skills 规范完全一致(SKILL.md + frontmatter + references),可以推测 Xcode 的 agent 底层就是 Claude:

xcode-skills/
├── audit-xcode-security-settings/
│   ├── SKILL.md                 # 主文件:frontmatter(name/description)+ 工作流
│   ├── references/              # 按需加载的详细文档(12 个)
│   │   ├── enhanced-security.md
│   │   ├── pointer-authentication.md
│   │   ├── hardware-memory-tagging.md
│   │   └── ...
│   └── scripts/
│       └── filter_build_settings.py   # 真·可执行脚本
├── c-bounds-safety/
├── device-interaction/
├── swiftui-specialist/
├── swiftui-whats-new-27/
├── test-modernizer/
└── uikit-app-modernization/

核心设计是渐进式披露(progressive disclosure):

渐进式披露流程:从只加载 name + description,到命中后读取 SKILL.md,再到按需读取 references

description 是触发器,SKILL.md 是工作流,references 是工具书。上下文窗口是稀缺资源,苹果在这套文件里把"什么时候加载什么"控制得很细。

渐进式披露的三层结构:description 常驻、SKILL.md 命中加载、references 按需读取

7 个 Skill 全景

Skill
一句话概括
体量
swiftui-whats-new-27
SDK 27 的 SwiftUI 新 API 和破坏性变更
SKILL.md 22 行 + 10 个 references
swiftui-specialist
SwiftUI 最佳实践(数据流、ForEach、本地化…)
19 行 + 10 个 references
audit-xcode-security-settings
安全构建设置审计,6 阶段工作流
216 行 + 12 个 references + 脚本
c-bounds-safety
C 语言 -fbounds-safety 扩展
30 行 + 5 个 references
uikit-app-modernization
UIScreen.main / 生命周期等 UIKit 现代化迁移
125 行 + 4 个 task 文件
test-modernizer
XCTest → Swift Testing 迁移
245 行,自包含
device-interaction
真机/模拟器上验证 App(截图、UI 层级、触摸合成)
132 行,自包含

下面按"信息量"从大到小逐个看。

swiftui-whats-new-27:iOS 27 SwiftUI 剧透

这是最有料的一个。它的 references 目录基本就是一份 SDK 27 SwiftUI 变更说明书。

@State 从 property wrapper 变成了宏

这是 SDK 27 最大的源码级破坏性变更。@State 改为宏实现后,老代码可能直接编译失败:

struct ContentView: View {
    var name: String
    @State private var counter: Int = 0   // 声明处有初始值

    init(name: String) {
        self.counter = 42        // ❌ error: Variable 'self.name' used
        self.name = name          //    before being initialized
    }
}

skill 里反复强调:"把 init 里的赋值换个顺序"这个直觉修法是错的——正确做法是删掉声明处的初始值,只在 init 里赋值。因为宏会合成真正的 backing storage,在其他存储属性赋值前碰 @State 属性就是过早使用 self。更隐蔽的是,即使编译过了,"声明处有初始值 + init 里再赋值"在运行时也不生效(body 看到的还是声明处的 0)。

苹果甚至在 description 里写明:遇到这类编译错误"you MUST consult this skill's references before answering"——明摆着是被模型的"想当然修复"坑过。

@State 宏迁移:报错代码与正确修法对比,调整赋值顺序是错误修法

任意容器的拖拽排序

以前 drag-to-reorder 基本是 List.onMove 专属,现在任何容器都行:

ScrollView {
    LazyVGrid(columns: columns) {
        ForEach(stickers) { sticker in
            StickerView(sticker)
        }
        .reorderable()                          // 加在 ForEach 上
    }
    .reorderContainer(for: Sticker.self) { difference in
        // 收到 ReorderDifference,自己应用到数据源
    }
}

ReorderDifference 带 sources(被移动的 item)和 destination(.before(id) 或 .end),数据更新完全交给开发者。

其他新 API 速览

  • AsyncImage
    :默认走标准 HTTP 缓存了(再也不会滚回来就重新加载);新增 AsyncImage(request:) 接 URLRequest 控制单次缓存策略,asyncImageURLSession(_:) 注入自定义 session。
  • swipeActions 不再是 List 专属
    :ScrollView + LazyVStack/LazyVGrid 里给行加滑动删除,容器标记 swipeActionsContainer() 即可。
  • Toolbar
    :visibilityPriority 控制空间不足时谁先进溢出菜单、topBarPinnedTrailing 固定项、toolbarMinimizeBehavior 滚动时收起导航栏。
  • alert / confirmationDialog 支持 item: 绑定
    ,对齐 sheet(item:) 的形态。
  • 文档型 App 新 API
    :ReadableDocument / WritableDocument 取代 FileDocument / ReferenceFileDocument(部署目标 ≥ 27 时新代码不应再用旧的),支持直接拿文件 URL、后台读写、增量包写入。
  • @ContentBuilder 统一了 result builder
    ,代价是少数地方源码不兼容(overlay/background 里 ShapeStyle 重载歧义等)。
  • 硬废弃
    :比如 visionOS 上的 statusBarHidden(已无效果,直接删调用)。

那个 3000 字符的 description

这个 skill 还有个值得单独说的细节:它的 frontmatter description 是一整段超长文本,把所有可能的触发场景全部枚举进去——编译错误的具体报错文案、每个新 API 的关键词、甚至"用户问 SwiftUI 有什么新东西"。因为 description 是 agent 决定要不要加载 skill 的唯一依据,所以苹果把它写成了一个"触发器字典"。这是很值得抄的 skill 写法。

swiftui-specialist:苹果官方的 SwiftUI 最佳实践

SKILL.md 本身只有 19 行,精华全在 10 个 references 里:view 结构拆分、数据流(@State/@Binding/@Observable)、Environment 性能陷阱、条件 modifier、本地化、ForEach 的 identity 要求、软废弃 API 清单。

开头第一句话很有意思:

This guidance was written and published by Apple. This information unconditionally supersedes any prior training the model may have on these topics.

苹果在直接对抗模型的过时训练数据——"不管你以前学过什么,以我为准"。swiftui-whats-new-27 里也有同款句式,还追加了一句 "Do not invent APIs or parameters that are not documented"(防幻觉)。

内容本身质量也很高,比如 dataflow.md 把 SwiftUI 的失效(invalidation)粒度讲得非常清楚:值类型逐字段比较、引用类型按指针比较、@Observable 按属性追踪——所以"只传 view 真正读的字段"这条规则对值类型入参至关重要,对引用类型基本不适用。这种深度的官方解释,公开文档里都不多见。

audit-xcode-security-settings:最工程化的一个

这是 7 个里最重的 skill:216 行 SKILL.md + 12 个 references + 1 个 Python 脚本,定义了一个完整的 6 阶段审计工作流:

安全审计的 6 阶段工作流:分析、应用设置、质询、验证、报告、可选跟进

6 阶段审计流水线与 Enhanced Security 的两面:Build Settings 与 Entitlements

几个亮点:

Enhanced Security 是个"能力"而不是单个开关——构建设置(ENABLE_ENHANCED_SECURITY,会级联出指针认证、栈零初始化、类型化分配器、C++ stdlib 硬化等)+ entitlements(com.apple.security.hardened-process 一族)两边都要配。skill 把必需 key、默认开/默认关的子选项、已废弃 key 的迁移路径全部写成了清单。

硬件内存标签(MTE / Memory Integrity Enforcement):reference 里明确写了硬件支持范围——iPhone 17 系列、M5 级别的 Mac/iPad/Vision Pro 及之后。每个内存分配和指针带 tag,tag 不匹配(use-after-free、堆溢出、double-free)直接 crash。推荐的灰度路径:先开 soft mode 只出模拟 crash 报告 → 修完 bug → 关 soft mode 转强制执行。

"决策文档"机制:审计结果落盘到 xcode-security-settings.md,记录每个设置的状态和"为什么关掉"的理由。下次再审计时,有记录的跳过、没记录的质询用户。相当于给 agent 设计了跨会话的记忆。

专用工具集曝光:这个 skill 的 Tool Preferences 一节透露了 Xcode agent 的内部工具——XcodeGlob、XcodeGrep、XcodeRead、XcodeLS、GetTargetBuildSettings、UpdateTargetBuildSetting、UpdateProjectBuildSetting、DeviceInteractionStartSession……并且严令禁止 agent 退回 find/grep/ls("find is forbidden")。还专门处理了输出超 token 限制的情况:结果写入文件后,用自带的 filter_build_settings.py 过滤,不许线性读大文件。

c-bounds-safety:给 C 指针装上边界

讲 -fbounds-safety 这个 C 语言扩展的。核心思想一句话:C 的指针是一个"点",只知道起点不知道终点;-fbounds-safety 把它变成一个"区间",编译器插入边界检查,把越界这种可被利用的安全漏洞降级成确定性的 trap。

注解体系(引入 ptrcheck.h 后可用):

注解
含义
ABI 兼容
__single
指向恰好一个元素或 NULL,禁止算术
✅(ABI 可见指针的默认值)
__bidi_indexable
宽指针:当前值 + 上界 + 下界,支持任意算术
❌(局部变量的默认值)
__indexable
宽指针:当前值 + 上界,只能往前走
❌
__counted_by(n)
n 个元素,如 int *__counted_by(count) buf
✅
__sized_by(n)
n 个字节
✅
__ended_by(p)
从指针到 p 的区间
✅
__null_terminated
0 结尾(C 字符串)
✅
__unsafe_indexable
无检查的逃生舱,用于和未适配代码互操作
✅

指针从点变成区间:传统 C 指针越界穿透 vs 宽指针越界触发确定性 trap

设计上最巧的一点是 ABI 兼容策略:函数参数、返回值、结构体字段这些 ABI 可见的指针默认变成 __single(布局不变,保持二进制兼容),只有局部变量这种 ABI 隐藏的指针才变成三倍宽的 __bidi_indexable。已适配和未适配的代码可以混链。

这个 skill 还有个独特的 frontmatter 字段 effort: high,并要求 agent 在动手改代码前必须完整读完三份文档(adoption-strategies、language-overview、common-patterns-and-pitfalls),除非"内容在活跃上下文中可验证地新鲜"。苹果对这种高风险改动的态度很谨慎。

uikit-app-modernization:从防御性条款反推 agent 的失败模式

功能本身不复杂:把 UIScreen.main、interfaceOrientation、AppDelegate 生命周期、对称 safe area 假设这些单窗口时代的 API,迁移到多窗口(Stage Manager、iPhone 镜像)时代的写法。比如最常见的替换:UIView/UIViewController 实例方法里内联使用的 UIScreen.main.scale → self.traitCollection.displayScale。

但这个 skill 真正好看的地方是它的 16 条 Core Principles——每一条都是一次失败案例的"事故报告":

  • "An empty diff for a file containing the target API is also a failure."(空 diff 也算失败——agent 爱偷懒跳过文件)
  • "A diff that collapses an if/else into sequential execution is a critical bug — both branches will execute unconditionally."(agent 改代码时删过 else 分支)
  • "Never remove the old method when adding a new overload."(agent 做 deprecate-and-forward 时顺手把旧方法删了)
  • "Stay in scope — no opportunistic cleanup."(agent 爱顺手修旁边不相关的废弃 API、删尾部空格)
  • "Do not get stuck weighing edge cases on simple files"(agent 在简单替换上过度思考,产出空 diff)

还有整整一个 Phase 专门防"静默丢文件":开工前列出全部待处理文件清单,结束前用 subagent 对账,任何没有 diff 也没有跳过理由的文件都算处理失败。大文件、宏地狱、C++ 互操作都不豁免。

这些条款合起来,基本就是一份"LLM 改代码的常见翻车清单",对任何写 coding agent prompt 的人都有参考价值。

test-modernizer:一张 XCTest → Swift Testing 对照表

最"工具书"的一个,245 行全部自包含,没有 references。核心是一套机械的映射规则:

XCTest
Swift Testing
final class FooTests: XCTestCasestruct FooTests
setUp()
 / tearDown()
init()
 / deinit(需要 deinit 时用 actor 或 final class)
func testEngineDoesNotStall()@Test func `Engine does not stall`()
(raw identifier 句子命名)
XCTAssertEqual(x, y)#expect(x == y)
try XCTUnwrap(x)try #require(x)
XCTestExpectation
 + fulfill()
await confirmation { ... }
XCTSkipIf(c)@Test(.disabled(if: c))
XCTExpectFailurewithKnownIssue

里面也有不少行为语义层面的提醒,不只是文本替换:

  • XCTest 同步测试跑在主线程、套件内串行;Swift Testing 并行跑在任意 task 上——所以 @MainActor 只给原来确实依赖主线程的测试加,共享状态的套件要加 @Suite(.serialized)。
  • continueAfterFailure = false
     的类,所有断言都得转成 try #require(而不是 #expect)才能保住"失败即停"的语义。
  • 循环跑同一段逻辑的测试,转成 @Test(arguments:) 参数化测试。

device-interaction:agent 长了眼睛和手

这个 skill 让 agent 能在模拟器/真机上验证 App:装包运行、截图、dump UI 层级、合成触摸事件。明确标注 "This is a SUBAGENT skill"——主 agent 改完 UI 代码后,spawn 一个子 agent 去设备上点一点、看一看,再把结论汇报回来,不污染主上下文。

主 Agent 派子 Agent 上设备验证的时序:安装运行、截图与 UI 层级、合成触摸、汇报结论

触摸合成是一套紧凑的命令 DSL:

t 100 200              # 点击
d 200 300              # 双击
t 200 600 f 200 200 0.3   # 上滑(滚动)
b h b h                # 按两次 Home 进入 app switcher
sender keyboard kbd hello\u{000A}   # 输入文字并回车
orientation landscapeLeft           # 转横屏

判定标准也写得很细:加载转圈、键盘弹出动画是"瞬态"不算 bug;文字重叠、截断、点了没反应、崩溃必须上报;空状态占位文案、表单没填完按钮置灰是"预期行为"别误报。

从这 7 个 skill 里能学到什么

把它们当成一个整体看,有几条苹果的 prompt 工程经验值得记下来:

1. description 就是路由器。 Skill 平时只有 description 在上下文里,所以触发条件要全部前置到 description——swiftui-whats-new-27 直接把编译器报错原文写进去,用户一贴报错就能命中。

2. 用权威声明对抗训练数据。 "unconditionally supersedes any prior training"、"Do not invent APIs"——当 skill 内容比模型的知识更新时,必须显式宣告优先级,否则模型会按旧知识"纠正"新 API。

3. 防御性条款是用失败案例喂出来的。 uikit-app-modernization 那 16 条原则,每条背后都是一次翻车。写 agent prompt 不是一次性工程,是持续把观察到的失败模式回填成规则。

4. 渐进式披露 + 强制阅读的分级。 普通 reference 按需读;高风险操作(bounds safety 改源码)则要求"必须先完整读完三份文档"。信息加载策略跟着风险走。

5. 把确定性的事交给脚本和工作流。 6 阶段流程、决策文档、对账清单、过滤脚本——能结构化的绝不靠模型自由发挥。模型负责判断,流程负责兜底。

6. 验证闭环。 改完代码不算完:build settings 要用 GetTargetBuildSettings 复核,UI 要派子 agent 上设备实际点一遍。

最后,这套东西本质上也是一份"半官方文档":iOS 27 的 @State 宏迁移、reorderable、Enhanced Security v2、MTE 硬件范围……很多细节比公开文档写得还直白。哪怕不关心 agent,把 xcrun agent skills export 跑一遍,当 SDK 变更说明书读,也很值。