iOS 项目把关卡、高清纹理或离线模型迁入 On-Demand Resources 后,安装包通常会明显变小,但构建成功不等于资源可用。最常见的事故不是编译报错,而是标签漏配、测试包意外嵌入全部资源,或发布产物中的资源包与代码请求不一致。云端 Mac 适合把这类检查固定成独立作业:每次从干净工作区归档,保留清单和校验和,再执行一次真实请求。
先定义可以验收的边界
ODR 验收至少覆盖三层:工程配置、导出产物和运行时请求。只看其中一层会留下盲区。
工程层需要确认 Target 已启用 On-Demand Resources,资源目录中的每个标签有明确用途。产物层检查 .assetpack、清单文件及文件哈希。运行时层则验证首次请求、释放资源和再次请求。
把“应用能启动”当成 ODR 验收标准是不够的。未被访问的关卡资源即使完全缺失,也不会阻止首页启动。
建议为每个标签保存四项信息:负责人、触发页面、预期资源类型、是否属于首次安装必需内容。它们应进入仓库内的普通文本清单,而不是只留在 Xcode 界面中。
用稳定标签控制分包粒度
标签不要直接跟文件夹数量一一对应。过细会产生大量小包和频繁请求,过粗则会让用户为一个页面下载整组无关资源。更稳妥的做法是按用户路径划分,例如 level-01、tutorial-audio、model-basic,并保持代码中的请求名与资源目录标签完全一致。
给标签清单设门禁
可以在仓库保存允许使用的标签:
level-01
level-02
tutorial-audio
model-basic
构建脚本从项目文件或生成后的资源信息中提取标签,排序后与该清单比较。新增标签必须伴随清单变更;删除标签时,也要同步检查代码中的 NSBundleResourceRequest 调用。这样能把拼写错误从运行阶段提前到合并阶段。
标签命名应只使用稳定的 ASCII 字符,避免空格、大小写混用和临时版本号。资源升级应由内容版本或校验和表达,不要不断创建 level-final-v2-new 一类无法维护的名称。
固定归档与导出条件
同一提交要使用固定的 workspace、scheme、Release 配置和导出选项。VMArm 节点上的作业可以先清空本次输出目录,但不要无差别删除全局缓存,否则难以判断变化来自资源还是环境。
set -euo pipefail
: "${WORKSPACE:?WORKSPACE is required}"
: "${SCHEME:?SCHEME is required}"
ROOT="$PWD"
ARCHIVE_PATH="$ROOT/out/Game.xcarchive"
EXPORT_DIR="$ROOT/out/export"
rm -rf "$ARCHIVE_PATH" "$EXPORT_DIR"
mkdir -p "$EXPORT_DIR"
xcodebuild \
-workspace "$WORKSPACE" \
-scheme "$SCHEME" \
-configuration Release \
-destination 'generic/platform=iOS' \
-archivePath "$ARCHIVE_PATH" \
clean archive
xcodebuild \
-exportArchive \
-archivePath "$ARCHIVE_PATH" \
-exportPath "$EXPORT_DIR" \
-exportOptionsPlist "$ROOT/ci/ExportOptions.plist"
本地验收通道可把 embedOnDemandResourcesAssetPacksInBundle 设为 true,先证明分包内容和请求逻辑正确。生产通道要使用实际分发方式对应的导出配置,不能沿用本地嵌入设置。两种通道的产物必须分目录保存,避免测试包覆盖待发布包。
对产物做三层检查
第一层检查文件是否存在。第二层检查 plist 是否能解析。第三层为所有资源文件生成稳定校验和,便于比较同一提交的两次构建。
set -euo pipefail
ARCHIVE_PATH="$PWD/out/Game.xcarchive"
EXPORT_DIR="$PWD/out/export"
REPORT_DIR="$PWD/out/report"
mkdir -p "$REPORT_DIR"
find "$ARCHIVE_PATH" "$EXPORT_DIR" \
-name '*.assetpack' \
-print | LC_ALL=C sort > "$REPORT_DIR/assetpacks.txt"
find "$ARCHIVE_PATH" "$EXPORT_DIR" \
-name 'AssetPackManifest.plist' \
-exec plutil -lint {} \; > "$REPORT_DIR/plist-lint.txt"
find "$ARCHIVE_PATH" "$EXPORT_DIR" \
-type f \
\( -name '*.car' -o -name '*.plist' -o -name '*.assetpack' \) \
-exec shasum -a 256 {} \; |
LC_ALL=C sort > "$REPORT_DIR/checksums.txt"
test -s "$REPORT_DIR/assetpacks.txt"
若 .assetpack 实际是目录,最后一条哈希命令不会直接计算目录内容,因此还应把该目录下的普通文件纳入检查。报告中应记录提交号、Xcode 版本、scheme 和导出配置文件的哈希,但不要写入签名私钥、令牌或完整凭据路径。
对比两次报告时,先区分“文件顺序变化”和“内容变化”。统一排序可以消除前者。若同一提交的哈希仍持续变化,再检查生成脚本是否写入时间戳、绝对工作区路径或随机标识。
运行时验证与故障定位
运行时测试要从干净状态开始。安装验收包后,请求一个非首装标签,记录请求开始、条件允许访问和释放资源三个事件。随后结束应用、清理测试环境,再执行一次,确认成功并非来自旧缓存。
常见症状的排查顺序
请求立即失败时,先核对代码标签与资源目录标签;请求长期等待时,检查资源包是否被正确导出以及测试网络;本地嵌入成功、生产通道失败时,重点比较两份 ExportOptions 和资源托管配置;只有部分资源缺失时,检查同名文件是否被分到多个标签,或资源是否仍属于主包。
不要用无限重试掩盖错误。为请求设置明确超时,失败日志只记录标签、错误域、错误码和阶段,不输出访问令牌或敏感路径。验收完成后保留报告、导出配置哈希和失败日志,清理临时包与测试缓存。这样下一次 ODR 变更出现偏差时,可以直接比较产物,而不是重新猜测 Xcode 做了什么。
常见问题
开发测试时是否应该把按需资源包嵌入应用?
可以。开发验收包可将 embedOnDemandResourcesAssetPacksInBundle 设为 true,先排除远端下载因素;生产导出则应按实际分发方式关闭嵌入并验证资源托管路径。
只检查 IPA 大小能确认 ODR 配置正确吗?
不能。还要检查 assetpack 数量、标签映射、AssetPackManifest.plist、资源校验和,并在干净设备或模拟器状态下执行首次请求与清理后的再次请求。
为下一次 Xcode 构建选择持续在线的云端 Mac
核对芯片、内存、存储、计费周期和节点区域后进入配置流程。实际可用状态以控制台实时返回为准。