最近,多位苹果生态播客主持人吐槽 Xcode 的复杂性,认为苹果必须改善 Mac 和 iOS 应用的“氛围编码”体验,让 Xcode 不再那么反人类。他们的批评没错——但说实话,我完全不明白他们为什么非要用 Xcode。只要花一点时间完成一次性的前期配置,你就可以彻底摆脱 Xcode,随心所欲地为 Mac 和 iOS 应用进行“氛围编码”。

如果你对后续步骤有任何疑问,直接把这篇博文扔给 Claude Code 或你喜欢的 LLM 编码工具,让它代劳。这正是它擅长的:帮你搞定那些你不想亲手触碰的事。
- Xcode.app 必须安装,但永远不需要打开。xcodebuild、notarytool、stapler、devicectl 都包含在 Xcode 内部,可以直接从 shell 调用。
- 有几个一次性步骤确实需要 GUI(或交互终端):登录 Apple ID、创建 Developer ID 证书、存储公证密码。完成这些后,后续的构建和部署都能完全在无头模式下运行。
- Mac 应用通过一个脚本
scripts/release.sh发布,你只需编写一次。它自动执行整个流程:归档 → Developer ID 签名 → 公证 → 附加票据 → 安装到 /Applications。 - 签名基于证书和钥匙串。签名密钥存储在登录钥匙串中;xcodebuild 会自动找到它。仓库中不存储任何密钥。
一次性设置是整个流程中唯一存在摩擦的地方,因此我们先解决它。
安装 Xcode
Xcode 必须安装,无法跳过——构建过程依赖于 Xcode.app 内部的工具。
安装 Xcode 后,请确保选中的命令行工具链是 Xcode 内部的,而不是 /Library/Developer/CommandLineTools。如果 xcode-select -p 输出的路径是 /Applications/Xcode.app/Contents/Developer,说明配置正确:
如果返回的是独立的 CommandLineTools 路径,请将其指向 Xcode:sudo xcode-select -s /Applications/Xcode.app/Contents/Developer。
注意:“命令行工具”这个名称容易让人混淆。有一个独立的命令行工具包可通过 xcode-select --install 获取,那是 /Library/Developer/CommandLineTools 版本,包含 clang 和 git,但不包含 iOS SDK、notarytool、devicectl 以及完整应用开发所需的一切。完整的工具链位于 Xcode.app 内部的 /Applications/Xcode.app/Developer,里面包含你需要的所有组件。既然已经安装了 Xcode,就完全没必要再安装独立的命令行工具。
安装 XcodeGen
仅有 Xcode 及其命令行工具,还不足以自动生成和管理 Xcode 项目。你需要 XcodeGen。可以从 GitHub 下载,或者使用 homebrew 安装:brew install xcodegen。
简单来说,Xcode 项目本质上是 macOS 让它看起来像文件夹的一个目录,里面存储着创建和编译应用所需的全部项目信息。Xcode 会不断修改文件和引用,给 git 仓库带来诸多麻烦。
XcodeGen 的做法是创建一个 project.yml(YAML)文件,记录所有项目设置,每次构建时用这个 YAML 文件重新生成整个 .xcodeproj 文件夹。只有 YAML 文件需要提交到 git,整个 .xcodeproj 可以从 git 的角度忽略掉。
一次性配置 Xcode
你仍然需要完成初始的 Xcode 设置,之后就可以彻底不再理会它。
Xcode 许可协议和附加组件
首先,要么打开 Xcode 接受许可协议并安装附加组件,要么通过命令行完成:sudo xcodebuild -license accept 和 sudo xcodebuild -runFirstLaunch。
在 Xcode 中设置你的 Apple 开发者账户
接下来,打开 Xcode,点击 Settings → Accounts,然后点击 + 添加你的账户。
注意:你必须拥有一个付费的 Apple 开发者账户,才能分发和公证你的应用。
你希望应用经过公证,这样安装到 Mac 或 iOS 设备上时,操作系统不会将其误判为恶意软件而直接删除。
创建 Developer ID Application 证书
完成后,创建一个 Developer ID Application 证书(Settings → Accounts → 你的 Apple ID → Manage Certificates… → + → Developer ID Application),该证书用于签署发布的 .app 包。
请注意,Developer ID Application 证书与你的 Apple Development 证书不同。Apple Development 身份用于在你自己的设备上构建和运行——比如推送到 iPhone、本地调试。Developer ID Application 身份用于通过 Gatekeeper 审查、能在他人 Mac 上运行的已公证 .app。发布脚本需要的是后者。
在 Xcode 中创建证书时,证书和私钥会安装到你的登录钥匙串中。私钥才是实际执行签名的关键,它无法重新下载——所以不要删除它,记得备份钥匙串。
遇到疑问,直接问你喜欢的 LLM,让它帮你搞定。反正它就是替你使用 Xcode 的。
存储公证凭据 —— 一次性,在终端中完成
公证会将签好名的应用上传到 Apple 进行恶意软件扫描。notarytool 使用你一次性创建的存储钥匙串配置文件来验证身份——它会提示输入应用专用密码,这个提示无法跳过:
xcrun notarytool store-credentials "MyApp" --apple-id "your-email@example.com" --team-id "YOUR_TEAM_ID" --password @keychain:AC_PASSWORD
几点注意事项:
- 为应用命名一个配置文件。不要使用其他应用的配置文件——在你的机器上能运行,但在别人机器上会静默失败。
- 应用专用密码不是你的 Apple ID 密码。请在
appleid.apple.com→ Sign-In & Security → App-Specific Passwords 生成一个。 - 这些密码在你修改 Apple ID 密码时会静默失效。notarytool 返回 401 无效凭据时,绝大多数情况是“去生成一个新的应用专用密码”,而不是“你的设置坏了”。
确认是否已存储成功:xcrun notarytool history --keychain-profile "MyApp"。
顺便提一句,你可以将应用专用密码放在 Claude Code 能访问的 1Password 保险库中。这样每次创建新应用时,可以直接告诉它去 1Password 保险库查询密码,自动创建公证凭据。使用 LLM 的初衷不就是不想手动处理那些你不想做的事吗。
设置 Local.xconfig 文件并将其添加到 .gitignore
实际签名需要你的团队 ID 和 bundle 前缀,将它们放在 Local.xconfig 文件中:
TEAM_ID = YOUR_TEAM_ID
BUNDLE_PREFIX = com.yourcompany
同样,如果不想自己写,让 Claude Code 或你选的 LLM 来创建这个文件。
设置智能体工具
创建部署脚本
应用部署通过 scripts/release.sh 脚本完成,放在仓库中。没有它,就没有自动构建管道。
可以让 Claude Code 来创建这个脚本:大致告诉它“我想归档、Developer ID 签名、公证、附加票据,然后把应用安装到 /Applications,全程不打开 Xcode。写一个脚本跑通整个流程,任何步骤失败都大声报错”。
它不需要你解释流程——流程本来就不是秘密:用 xcodebuild 归档,用 -exportArchive 和 ExportOptions.plist 导出,用 notarytool --wait 提交,用 stapler 附加票据,用 spctl 校验。这是发布 Developer ID Mac 应用的标准化文档化方式,模型知道。它需要你提供的是项目特定内容:scheme 名称、团队 ID、公证配置文件名称、最终安装位置。
然后它会写出初稿,你运行一下,它失败了,再修改一下。这个循环不是失败模式,这就是过程。AI 工作流总是进行中的工作,但很快你就能停下来,开始真正干活。
以下是一个真实应用仓库中的脚本:
#!/bin/bash
set -euo pipefail
cd "$(dirname "$0")/.."
# Preflight checks
command -v xcodegen >/dev/null 2>&1 || { echo "xcodegen is required"; exit 1; }
xcrun notarytool history --keychain-profile "${TZED_NOTARY_PROFILE:-Default}" >/dev/null 2>&1 || { echo "Notary profile not found"; exit 1; }
# Generate project
xcodegen generate
# Archive
xcodebuild -scheme MyApp -configuration Release -archivePath build/MyApp.xcarchive archive
# Export
xcodebuild -exportArchive -archivePath build/MyApp.xcarchive -exportPath build/MyApp -exportOptionsPlist ExportOptions.plist
# Notarize
xcrun notarytool submit build/MyApp/MyApp.app --keychain-profile "${TZED_NOTARY_PROFILE:-Default}" --wait
# Staple
xcrun stapler staple build/MyApp/MyApp.app
# Verify
spctl -a -v build/MyApp/MyApp.app
# Install
cp -R build/MyApp/MyApp.app /Applications/
# Re-verify after copy
spctl -a -v /Applications/MyApp.app
看起来比实际复杂,但其实就是一系列必须执行的步骤。再次强调,这就是为什么你要跟 LLM 对话:告诉它你想要什么,让它帮你搭工作流。
几个注意事项:
set -euo pipefail会在任何命令失败时立刻停掉脚本,不会继续执行。不会有看起来成功实则未完成的状态。cd "$(dirname "$0")/.."让脚本不管从仓库根目录还是深入三级目录调用,都能跳到仓库根目录。- 预检块检查
xcodegen是否存在以及公证配置文件是否已存储,免得在注定失败的第五步上浪费五分钟做归档。 - 最后两步重新验证已安装的包,而不只是导出的包。这算双重保险,但曾遇到过复制步骤静默损坏包的情况,宁可从脚本里发现,也不愿三天后 Gatekeeper 删掉应用时才察觉。
创建 CLAUDE.md 或 AGENTS.md
release.sh 提供一键部署。CLAUDE.md(或 AGENTS.md,适用于几乎所有其他模型)是让智能体真正使用它的方法,不用每次都手动告诉它。
在和 Claude 反复讨论构建过程后,让 Claude 创建了 CLAUDE.md。现在每次创建新应用时,只要告诉它参考其他应用的仓库,用同样的方法就行。
以上就是全部一次性设置。从现在开始,不用鼠标了。
构建实际运行方式 —— 无 GUI 参与
以下所有操作都是普通的命令行调用。Xcode.app 永远不会启动——这些工具位于其中,但可以独立运行。Claude Code 也是通过 shell 执行这些的。
快速、未签名检查
对于“能不能编译 / 测试是否通过”,完全不需要签名:
xcodebuild -scheme MyApp -configuration Debug -sdk iphonesimulator CODE_SIGNING_ALLOWED=NO
CODE_SIGNING_ALLOWED=NO 给你一个临时签名构建:可以编译并在模拟器里跑,但 Gatekeeper 会拒绝它,iCloud KVS 和 App Group 等权限不会绑定。这是快速的内部循环。
Mac 发布流程
一条命令跑完整个可发布流程——就是上面那个脚本:
./scripts/release.sh
归档、Developer ID 导出、公证、附加票据、验证、安装。任何一步失败,都会停止并告诉你哪里出错。需要不同的公证配置文件?覆盖它:TZED_NOTARY_PROFILE=。
无头部署到真实 iPhone
iOS 没有公证步骤——那是 Mac 分发的概念。把构建部署到连接的 iPhone 是 xcodebuild 加上 devicectl,两者都在 Xcode 的工具链里:
devicectl list devices
xcodebuild -scheme MyApp -configuration Debug -destination 'platform=iOS,id=' -allowProvisioningUpdates
devicectl list devices 列出已连接和配对的设备及其 UDID。设备构建使用 Apple Development 身份(不是 Developer ID)加上开发配置文件签名,-allowProvisioningUpdates 会自动帮你获取那个配置文件。
无 GUI 时代码签名工作原理
如果以前只在 Xcode 里勾选框来签名,值得了解一下实际发生了什么——因为构建时根本不需要 GUI。
私钥执行签名。当你创建 Developer ID Application 证书时,Apple 颁发证书,你的 Mac 生成匹配的私钥,两者都存到登录钥匙串里。codesign(xcodebuild 会调用它)用私钥对二进制文件签名;证书 —— 它链接到 Apple 的根证书 —— 被嵌入,任何人都可以验证。
自动签名为你选择身份。发布脚本使用 signingStyle: automatic,所以 xcodebuild 会根据团队 ID 选择正确的身份,并即时从 Apple 获取所需的配置文件。没有任何配置文件提交到仓库。
权限在签名时绑定。每个目标都有一个 .entitlements 文件(沙盒、网络客户端、iCloud KVS、App Group)。这些只有在应用使用了真实的团队身份签名时才生效——这也是为什么临时签名构建无法发布:没有团队前缀,iCloud 和 App-Group 权限会静默不绑定,你会花一个小时想破头为什么键值存储是空的。
公证不是签名。签名证明谁构建了应用。公证是单独的步骤:Apple 扫描已签名的应用,查恶意软件并颁发片据;附加票据让 Gatekeeper 在离线时也能信任应用。对于隐藏 UI 的菜单栏应用(LSUIElement),公证是防止 XProtect 标记它的关键。
密钥永远不会接触 git。签名私钥存在登录钥匙串里。公证应用专用密码存在 notarytool 钥匙串配置文件里。两者都不会写入仓库。
可以手动验证任何已签名的构建:codesign -dvvv /Applications/MyApp.app 和 spctl -a -v /Applications/MyApp.app。
智能体实际使用的工具
这里没有魔法。Claude Code 通过普通的、非交互式 shell 驱动所有这些——没有特殊的“构建”MCP 服务器或插件在背后做聪明的事情。它用的是 xcodebuild、xcrun notarytool、xcrun stapler、spctl、codesign、devicectl、xcodegen 和 swift。标准的 CLI 工具,跟我们自己用的一样。
粘合剂是前面提到的 CLAUDE.md。它告诉智能体 notary-profile 的约定、两个构建路径,以及 shipping 意味着 release.sh。结果是 Claude 可以直接跑它,不用在每个会话里重新解释一遍。
唯一需要交互的步骤是 notarytool store-credentials——这是选择而不是限制:你可以传 --password 并把脚本写完整,但那样会把应用特定密码留在 shell 历史记录里。手动输一次,让钥匙串或 1Password 保存它,下游所有东西都自动了。
为什么 Xcode 永远不需要打开
把 GUI 工作流和无头工作流放在一起对比,整个流程就完全对上了:
- GUI 只在一次性凭证设置时需要。之后整个生命周期都是可脚本化的——这正是
release.sh最终做的事,也正是智能体可以端到端拥有它的原因,而你则可以去做比看进度条更有趣的事。
如果你想自己试一下,顺序是:安装 Xcode 和 xcodegen,做凭证设置,然后跟 Claude 一起坐下来,构建你自己的 release.sh 和 CLAUDE.md。最后这部分是实际工作,最多一两个小时。之后,“发布新构建”就变成一句话的事。第一个应用设置好之后,Claude Code 可以复制同样的设置用到未来的应用上。
