App Store 一直提示“二进制文件无效”,从本地乱改到 Xcode Cloud 终于过了
今天被 App Store Connect 折腾得有点没脾气。
本来只是给棱镜音乐提交一个新包,心里想的是:代码能跑,Archive 成功,签名也没报错,那应该就是上传、等审核、然后睡觉。结果 Apple 反手给我来了一句:
ITMS-90111: Unsupported SDK or Xcode version - App submissions must use the latest Xcode and SDK Release Candidates (RC).
更烦的是,在 App Store Connect 页面上,它最开始给人的感觉就只有“二进制文件无效”。没有特别清楚地告诉你是哪个文件错了,也没有指出是哪一个配置不对。于是我就开始了一轮非常典型的 iOS 开发者自我怀疑:是不是 build 号?是不是签名?是不是扩展?是不是内购?是不是又被什么奇怪的私有 API 扫到了?

最后确实解决了,但中间走了不少弯路。记录一下,免得下次又被自己坑一遍,顺便给后来者提供一点思路。
一开始我以为只是 build 号不对
最开始查包的时候,发现主 App 和 Live Activity 扩展的 build 号不一致。
主 App 是一个号,扩展又是另一个号。App Store 对这种东西很敏感,尤其是主包、扩展、内嵌 framework 一起提交的时候,只要有一个地方版本号不统一,就很容易被判成无效二进制。
所以第一步就是把所有 CURRENT_PROJECT_VERSION 统一。
一开始改到了 11,后来因为 App Store Connect 已经记录过失败的 build,又继续改到 12,最后为了重新走 Xcode Cloud,又加到 13。
这一步是必要的,但不是最终根因。
这个坑给我的第一条经验是:以后 iOS 项目只要带扩展,build 号一定要主 App、扩展、framework 全部统一,不要只看主 target。
然后我开始怀疑隐私清单
接着又查到工程里用了文件时间戳、UserDefaults 之类的 API,但没有隐私清单。
现在 Apple 对 privacy manifest 查得越来越严,尤其是 Required Reason API,如果包里用了但是没声明,很容易在上传阶段就被拦。
于是我加了 PrivacyInfo.xcprivacy,声明了:
- File Timestamp
- UserDefaults
- 不收集数据
- 不追踪用户
这一步也应该做,而且是迟早要补的。但这次的 ITMS-90111 不是它导致的。
这也是最折磨人的地方:你会发现很多地方“看起来都可能有问题”,于是每个都值得修,但每个修完以后错误还在。
中间还误会了一次内购
还有一个很容易误判的地方:主 App 声明了 In-App Purchase 能力,也在 Info.plist 里放了 PremiumProductIDs,但看 entitlements 的时候发现里面没有 In-App Purchase。
当时我第一反应是:是不是内购能力声明和 entitlements 不一致,所以被 Apple 判无效?
于是中间还动过一个很危险的念头:把内购相关声明删掉。
后来冷静下来才发现,这是误会。In-App Purchase 本来就不像 Push、Apple Pay 那样一定会出现在 app entitlements 里。内购能力更多是 App ID / App Store Connect / StoreKit 商品配置层面的东西,不能简单用“entitlements 里有没有”判断。
所以后面又把这些东西恢复了:
PremiumProductIDs- In-App Purchase capability
- 代码里读取商品 ID 的逻辑
顺手还修了一个真实问题:因为工程用了 GENERATE_INFOPLIST_FILE=YES,最终打进 IPA 的 Info.plist 一开始并没有带上 PremiumProductIDs。也就是说源码里写了,但包里没有。
最后我把商品 ID 放进了生成 Info.plist 的 build setting,并且让代码同时兼容数组和字符串两种形式。
这个问题虽然不是“二进制文件无效”的根因,但如果不修,内购运行时肯定会出问题。
看到一篇 Reveal 的帖子,又去查私有 API
后来我翻到一篇老文章,说作者 App Store 上传后一直提示二进制文件无效,最后发现是工程里带了 Reveal.framework,被 Apple 当成私有 API 或调试框架处理。
这类文章很容易让人重新燃起希望,因为它给了一个很具体的方向:是不是我包里也混进了什么调试框架?
于是我又去扫了一遍:
- Reveal
- FLEX
- DoraemonKit
- Injection
- Cycript
LSApplicationWorkspaceprefs:rootPrivateFrameworks
结果都没有。
源码里倒是出现过 hasReveal 这种变量名,但那只是歌词逐字高亮里的“显示进度”语义,不是 Reveal 调试工具。
所以这条路也排除了。
这一步的收获是:私有 API 方向确实值得查,但不能看到一个关键词就吓自己。要看最终 IPA 里有没有真正的 framework 和符号。
本地打包看起来一切都对
中间我还把整个 archive/export 流程重新走了一遍。
这里又踩了一个小坑:Xcode archive 出来的 .xcarchive 不等于可以直接上传的 App Store 包。
本地 archive 的时候,它可能还是 Apple Development 签名,get-task-allow=true。真正要上传 App Store Connect 的,是用 xcodebuild -exportArchive 导出的 IPA。
导出以后才会变成:
- Apple Distribution 签名
get-task-allow=false- 包含 App Store provisioning profile
- 主 App 和扩展都重新签好
另外还有一个细节:Xcode 导出时默认可能会自动管理 build 号,导致工程里是 11,导出的 IPA 变成 12。后来我把 manageAppVersionAndBuildNumber 关掉,才让最终 IPA 的 build 号和工程一致。
当时本地检查结果看起来非常漂亮:
- build 号统一
- Apple Distribution 签名
- arm64 架构
- privacy manifest 存在
- 内购商品 ID 存在
- 没有 Reveal/FLEX
然后上传,还是失败。
这时候就真的有点烦了。因为你手里所有本地工具都告诉你“这个包没问题”,但 Apple 那边就是一句“Unsupported SDK or Xcode version”。
换 Xcode 26.6,也还是不行
后来我按 Apple 提示去看 releases 页面,发现当前应该用 Xcode 26.6,也就是 17F113。
本机原来的 Xcode 是 26.5,于是下载了 Xcode 26.6,然后用 DEVELOPER_DIR 指到新 Xcode 重新打包。
本地查出来也确实变成了:
DTXcode = 2660
DTXcodeBuild = 17F113
看起来这次应该稳了。
结果上传以后,还是 ITMS-90111。
这一步最让人崩,因为错误信息明明说 Xcode 版本不支持,我已经换成支持版本了,它还是不认。
后来继续扒 IPA 的 Info.plist,才看到另一个字段:
BuildMachineOSBuild = 26A5368g
这下终于对上了。
我的机器是 macOS 27 beta。
也就是说,即使用了正式 Xcode 26.6,只要它运行在 beta macOS 上,最终包里还是会留下 beta 系统的构建环境信息。App Store Connect 很可能把这个也归到“不支持的 SDK 或 Xcode 版本”里一起拒掉。
这就很坑,因为错误文案没有直接说“你用了 beta macOS”。它只说 Unsupported SDK or Xcode version。
真正的转折:评论区一句话
后面我在帖子评论里看到有人提到一个方向:不要在 beta 系统上折腾提交包,换干净环境,或者直接用 Xcode Cloud。

这句话一下把前面的线串起来了。
我本地不管怎么换 Xcode,本质上还是在同一台 beta macOS 上打包。只要构建机器环境不被 Apple 接收,本地继续修代码、改 plist、改 build 号,都只是在绕圈。
所以最后决定:转 Xcode Cloud。
不是因为 Xcode Cloud 神奇,而是因为它的构建环境是 Apple 自己提供的干净环境,不会带我本机这个 beta macOS 的 BuildMachineOSBuild。
Xcode Cloud 第一次也没一次成功
不过转 Xcode Cloud 以后也不是一键结束。
第一次我下载到的是一个 zip,解压以后发现里面像是 Debug 包。
当时又懵了一下:不是说云端打包吗?怎么给我一个 debug 产物?
后来才搞明白:Xcode Cloud 页面里下载到的 zip,很多时候只是 build artifacts 或日志归档,不是给你手动上传 App Store 的 IPA。
真正正确的方式不是下载 zip 再上传,而是在 workflow 里配置:
- Archive
- Release
- iOS
- Xcode 最新正式版
- 上传到 App Store Connect / TestFlight
也就是说,Xcode Cloud 应该直接把 archive 后的 build 上传到 App Store Connect。你在本地下载那个 zip,多半只是为了调试构建过程,不是最终提交物。
后来把 workflow 改对以后,重新跑,build 13 出来了。
这次终于正常了。

这次完整流程大概长这样
flowchart TD
A[App Store 提示二进制文件无效] --> B[先怀疑 build 号]
B --> C[统一主 App / 扩展 / framework 的 CURRENT_PROJECT_VERSION]
C --> D[补 PrivacyInfo.xcprivacy]
D --> E[误以为 IAP entitlements 异常]
E --> F[恢复内购并修 PremiumProductIDs 打包丢失]
F --> G[看到 Reveal 私有 API 帖子]
G --> H[扫描 IPA: 没有 Reveal / FLEX / 私有 API]
H --> I[用 Xcode 26.6 重新 archive/export]
I --> J[仍然 ITMS-90111]
J --> K[发现 BuildMachineOSBuild 是 macOS beta]
K --> L[转 Xcode Cloud]
L --> M[第一次拿到 zip/debug artifacts]
M --> N[改 workflow: Archive + Distribute]
N --> O[云端生成 build 13]
O --> P[终于搞定]
最后真正有用的结论
这次折腾下来,我觉得最有用的结论有几个。
第一,看到“二进制文件无效”不要只盯着代码。
很多时候它不是代码逻辑错,而是构建环境、签名、SDK、Archive/Export 流程的问题。
第二,ITMS-90111 不一定只是 Xcode 版本。
它的文案写的是 Unsupported SDK or Xcode version,但实际可能包含:
- Xcode 版本不对
- SDK 版本不对
- 用了 beta Xcode
- 用了 beta macOS 构建
- 包里残留旧工具链信息
第三,带扩展的 App,build 号要全包统一。
主 App、extension、framework 不统一,迟早出事。
第四,In-App Purchase 不要用 entitlements 有没有来判断。
这次我差点把内购删掉,幸好后来恢复了。内购本来就不是那种一定会写进 entitlements 的能力。
第五,Xcode Cloud 的 zip 不等于上架包。
如果目标是上传 App Store Connect,workflow 要走 Archive + Distribute。不要下载一个 zip,解压看到 Debug,就以为那是最终 IPA。
第六,如果本机是 beta macOS,尽量别拿它打 App Store 提交包。
调试可以,开发可以,自己装着玩也可以。但真正提交审核,最好用正式 macOS + 正式 Xcode,或者干脆交给 Xcode Cloud。
结尾
这次最烦的地方不是问题多,而是每一步都像真问题。
build 号不统一,确实要修。
privacy manifest 没补,确实要补。
内购商品 ID 没进最终 IPA,确实会影响功能。
Reveal 私有 API 的方向,也确实有人踩过。
Xcode 版本不对,也确实会被拒。
但真正让 build 过不去的,是我一直忽略的构建机器环境:macOS beta。
回头看,这就是一次很典型的 Apple 审核排查:错误信息给得很省,开发者自己把所有可能性扫一遍,最后靠一个评论区提醒,才发现根因不在代码里。
好在最后 build 13 终于过了。
这次记住了:以后要提交 App Store,别在 beta 系统上硬打包。真的容易把人折腾到怀疑人生。