一台 Mac 升级到 macOS 27.0 后,某个遗留 iOS 项目的构建卡在了资源编译阶段。项目还在使用 Xcode 15.2,短期内没有时间完成新版 Xcode 的迁移适配,也没有另一台旧 macOS 机器可以接手构建。
这次最终采用的办法是:保留 Xcode 15.2 的主工具链和 iPhoneOS 17.2 SDK,只把其中的 AssetCatalogSimulatorAgent 换成 Xcode 16.4 自带的对应文件。替换后,资源编译、完整归档和测试分发恢复,随后又检查了资源产物,并在两台不同系统、不同倍率的真机上验证了典型页面。
这是一项有明确验证范围的临时兼容处理。Apple 列出的 Xcode 15.2 支持环境是 macOS Ventura 13.5 至 macOS Sonoma 14.x,macOS 27 不在这个范围内。下面的做法不属于 Apple 官方支持的工具混用方案。官方兼容列表
错误发生在辅助进程握手之前
当时的构建环境如下:
| 项目 | 本次环境 |
|---|---|
| 操作系统 | macOS 27.0 |
| 主构建工具 | Xcode 15.2,构建号 15C500b |
| 真机 SDK | iPhoneOS 17.2 |
| 替换文件来源 | Xcode 16.4 |
CI 归档时,Assets.xcassets 编译报错。去掉工程路径后,核心信息是:
text
Assets.xcassets: error: Failed to launch AssetCatalogSimulatorAgent via CoreSimulator spawn
Failed to handshake with platform tool
Failed to open connection over FIFOs with platform tool
Failed to open FIFOs for handshaking with platform tool
AssetCatalogSimulatorAgent exited before we could handshake
** ARCHIVE FAILED **
这段错误把调查范围收窄到了一个具体环节:资源编译需要启动的辅助进程,在完成握手前就退出了。仅凭这些日志,还不能断定是哪项系统内部机制导致退出,也不能直接认定某张图片已经损坏。
另一个容易误判的地方是工具位置。AssetCatalogSimulatorAgent 位于 Xcode 的 iPhoneSimulator.platform 目录中,但本次归档目标是真机,资源编译仍然走到了这条辅助工具链。目录名带有 Simulator,并不能把它从真机打包问题中排除。
后续对同一份资源目录做了替换前后的编译对照:旧辅助工具下握手失败,只替换辅助工具后,编译退出码变为 0,并生成了 Assets.car。
这个对照支持将故障定位到旧辅助工具与当前运行环境的兼容链路。它还不足以解释全部底层原因,因此没有继续把某个未经直接日志证实的系统文件缺失写成确定结论。
改动范围只有一个辅助可执行文件
源文件和目标文件在各自 Xcode.app 内的相对路径相同:
text
Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/Library/Xcode/Overlays/AssetCatalogSimulatorAgent
具体做法是把 Xcode 16.4 中的这个文件复制到 Xcode 15.2 的对应位置。编译器、SDK 和其余构建工具保留原样。

图为生成的方案示意,不是实际构建截图。连线只说明工具与资源编译的关系,不代表完整的内部调用时序。
原文件在 Xcode.app 外单独备份,并记录 SHA-256。替换文件来自可信的 Xcode 安装,保留 Apple 签名,没有临时重签名,也没有关闭 SIP、Gatekeeper 或移植旧 macOS 的系统二进制。
替换后执行了 codesign --verify --strict,验证的是这个辅助文件自身的签名。单个文件签名有效,不等于修改后的整个 Xcode.app 外层资源封装签名仍然完整。 这是两件不同的事。
先备份,再替换,最后用原工具链复测
下面的命令是从本次操作中提炼出的示例。/path/to/...、ExampleApp 和构建配置均为占位符,需要按实际环境修改。它们用于说明操作顺序,不是适用于任意 Xcode 和 macOS 组合的一键修复脚本。
操作前先暂停所有使用目标 Xcode 的构建,确认两套 Xcode 来源可信。以下命令在同一个 Bash 会话中按顺序执行,任一步失败都先停止检查。
1. 确认文件,留下原始备份
bash
set -euo pipefail
OLD_XCODE='/path/to/Xcode_15.2.app'
DONOR_XCODE='/path/to/Xcode_16.4.app'
HELPER_REL='Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/Library/Xcode/Overlays/AssetCatalogSimulatorAgent'
TARGET="$OLD_XCODE/$HELPER_REL"
DONOR="$DONOR_XCODE/$HELPER_REL"
BACKUP_DIR='/path/to/backup/assetcatalog-agent-before-replacement'
test -f "$TARGET"
test -f "$DONOR"
test -x "$TARGET"
test -x "$DONOR"
codesign --verify --strict "$DONOR"
# 使用全新的备份目录,避免覆盖之前的原始备份。
test ! -e "$BACKUP_DIR"
mkdir -p "$(dirname "$BACKUP_DIR")"
mkdir "$BACKUP_DIR"
shasum -a 256 "$TARGET" "$DONOR" > "$BACKUP_DIR/before.sha256"
cp -p "$TARGET" "$BACKUP_DIR/AssetCatalogSimulatorAgent.original"
cmp -s "$TARGET" "$BACKUP_DIR/AssetCatalogSimulatorAgent.original"
date -u '+%Y-%m-%dT%H:%M:%SZ' > "$BACKUP_DIR/replaced-at.txt"
这里的备份目录应位于两套 Xcode.app 之外。除文件摘要外,还应在本地操作记录中保留两个 Xcode 的版本、文件来源和目标位置,便于后续确认机器处于什么状态。这些本地记录不需要随文章或测试包公开。
2. 替换文件并核对
bash
cp -p "$DONOR" "$TARGET"
cmp -s "$DONOR" "$TARGET"
test -x "$TARGET"
shasum -a 256 "$DONOR" "$TARGET" > "$BACKUP_DIR/after.sha256"
codesign --verify --strict "$TARGET"
cmp 检查内容是否相同,摘要文件用于保留审计记录,签名校验用于检查复制后的辅助文件。只有复制操作确实受到文件权限限制时,才为该操作提升权限。签名校验失败时应查明来源或复制过程的问题,不要通过临时重签名让命令表面通过。
3. 明确指定 Xcode 15.2,逐步恢复构建
bash
export DEVELOPER_DIR="$OLD_XCODE/Contents/Developer"
xcodebuild -version
xcrun --sdk iphoneos --show-sdk-version
本案例应确认工具版本仍为 Xcode 15.2、构建号 15C500b,iPhoneOS SDK 仍为 17.2。这样可以避免机器上装有多套 Xcode 时,实际切换了工具链,却把构建成功归因于单文件替换。
先复用失败日志中的完整 actool 参数,对同一资源目录重新编译,保持原有平台、部署目标、设备类型等参数。确认退出码和 Assets.car 输出后,再运行工程原有的 Archive、导出及签名流程。
下面只展示归档、导出的命令形态。项目已有的配置、签名参数和导出配置应沿用原流程:
bash
xcodebuild \
-workspace '/path/to/ExampleApp.xcworkspace' \
-scheme 'ExampleApp' \
-configuration '<原构建配置>' \
-destination 'generic/platform=iOS' \
-archivePath '/path/to/output/ExampleApp.xcarchive' \
archive
xcodebuild -exportArchive \
-archivePath '/path/to/output/ExampleApp.xcarchive' \
-exportPath '/path/to/output/export' \
-exportOptionsPlist '/path/to/ExportOptions.plist'
4. 需要回滚时,恢复原文件
回滚前同样需要停止相关构建。使用前面记录的目标路径和备份目录,将原文件恢复到原位置:
bash
test -f "$BACKUP_DIR/AssetCatalogSimulatorAgent.original"
cp -p "$BACKUP_DIR/AssetCatalogSimulatorAgent.original" "$TARGET"
cmp -s "$BACKUP_DIR/AssetCatalogSimulatorAgent.original" "$TARGET"
shasum -a 256 "$TARGET"
codesign --verify --strict "$TARGET"
恢复后,把目标文件摘要与 before.sha256 中记录的原始摘要核对。回滚恢复的是原工具状态,原先的系统兼容性问题也可能随之重新出现。
编译通过后,还要检查资源输出
这次真正需要确认的是:新辅助工具生成的资源,是否仍能在应用中正常使用。
AssetCatalogSimulatorAgent 是构建阶段使用的工具。最终 IPA 检查中没有发现这个辅助可执行文件,包内记录的工具链信息也仍然是:
text
DTXcode = 1520
DTXcodeBuild = 15C500b
DTSDKName = iphoneos17.2
这些结果支持主工具链和 SDK 保持不变的结论。但辅助工具会参与资源输出的生成,文件本身没有进入 IPA,不能代替对资源产物的检查。
本次按下面几层完成了验证。资源数量使用区间描述,省略具体资源名和业务页面名称。
| 层次 | 检查内容 | 本次结果 |
|---|---|---|
| 最小复现 | 同一资源目录在替换前后执行资源编译 | 替换前握手失败,替换后退出码为 0,生成 Assets.car |
| 本地完整构建 | 测试、生产配置归档和 IPA 导出 | 均成功,代表构建及产物检查通过,不代表生产业务全量验收 |
| 源资源 | 800 多张 PNG/JPEG 完整解码 | 通过 |
| 资源覆盖 | 350 多个有效图片名称映射到新包,比较尺寸和倍率 | 未发现缺失或不一致 |
Assets.car |
assetutil -Z 校验新旧产物,比较 2,200 多条 Image 记录的名称、倍率、idiom、像素尺寸和 SHA1Digest |
校验通过,上述对比项一致 |
| 独立图片 | 图标、启动图解码及像素检查 | 两种本地配置共 50 个文件通过,最终 CI 产物另有 25 个独立图片完成解码检查 |
| 完整 CI | 依赖安装、资源编译、Archive、导出签名、dSYM、测试分发上传和产物归档 | Jenkins 最终为 SUCCESS,用时约 4 分钟,测试分发上传成功 |
| 最终 IPA | ZIP CRC、codesign --verify --deep --strict、Assets.car、工具链元数据、app 与 dSYM 的 UUID |
均通过,归档文件与实际验证文件的 SHA-256 一致 |
其中,Assets.car 的记录对比可以帮助发现覆盖、倍率和内容差异,但它不能证明每个页面的资源加载代码都执行过。运行时仍需要真机检查。
遇到解码失败,先分清图片格式
检查包内独立图片时,遇到了 Apple 优化过的 CgBI PNG。直接交给 Pillow 解码可能失败,不能仅凭这一点判定安装包图片损坏。
本次先通过 Apple 的图像处理工具进行转换,再完整解码并比较 RGBA 像素。使用 sips 转换单张文件的示例是:
bash
sips -s format png \
'/path/to/extracted/ExampleIcon.png' \
--out '/path/to/decoded/ExampleIcon.png'
转换结果应写入单独目录,不要覆盖提取出的原文件。转换成功后仍需实际解码,不能只检查输出文件是否存在。
Apple 的归档文档也说明过 iOS 优化 PNG 需要还原处理才能供普通工具查看,并给出了 pngcrush -revert-iphone-optimizations 的历史用法。它可以帮助理解格式差异,具体工具是否存在仍应以当前 Xcode 为准。Viewing iOS-Optimized PNGs
另外,构建脚本如果会给图标添加环境或版本角标,最终图片相对源图出现像素变化可能是预期行为。对比时要把这类已知加工与真正的图片损坏分开。
两台真机分别验证了什么
真机验证覆盖了不同系统和屏幕倍率,但两台设备使用的产物并不完全相同。
| 设备 | 安装产物 | 实际检查范围 |
|---|---|---|
| iPhone 7,iOS 15.5,@2x | 本地测试 IPA | 安装、启动、登录、首页、功能入口、列表与详情、个人页,以及关闭后重新启动 |
| iPhone 12 Pro Max,iOS 26.7,@3x | 本地包及最终 CI IPA | 安装、启动、首页、功能入口和个人页 |
这些典型页面的图片显示正常,未观察到崩溃。最终 CI IPA 实际安装检查是在 iOS 26.7 设备上完成的,iOS 15.5 使用的是本地产物。虽然相关资源输出已经做过对比,也不能把它写成最终 CI 包在两台设备上都安装验证过。
工程声明的最低支持版本是 iOS 10,本次实际测试的最低系统则是 iOS 15.5,iOS 10 至 iOS 14 没有验证。声明的部署目标与实际测试覆盖范围需要分开看。
验证也没有覆盖全部业务、逐张素材的运行时展示或长时间稳定性。日志中仍有空图片名、主题和子 bundle 资源提示,未观察到对应的可见异常,因此不能将结果描述为零告警或所有资源绝对正常。
这项处理能解决到哪里
在本次 macOS 27.0、Xcode 15.2 的环境中,替换为 Xcode 16.4 的 AssetCatalogSimulatorAgent 后,资源编译和完整 CI 恢复,产物检查及两台不同系统、不同倍率设备的典型用例验证,未发现由该处理引起的图片异常或崩溃。
这足以支持它作为当前遗留项目维护的临时兼容办法,不能据此推广到所有系统、Xcode、项目和资源格式。本文也没有验证 App Store 提交或审核结果。
后续如果重装、更新 Xcode,替换文件可能被覆盖。macOS 或 Xcode 发生变化后,需要重新确认辅助文件状态,并重跑资源编译、产物检查和关键真机用例。原文件备份与替换记录应一直保留,直到这套临时构建环境退出使用。